diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 0af93bfc90..3f8559e63f 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -29,7 +29,11 @@ "subcontract", "backflush_raw_materials_of_subcontract_based_on", "column_break_11", - "over_transfer_allowance" + "over_transfer_allowance", + "section_break_xcug", + "auto_create_subcontracting_order", + "column_break_izrr", + "auto_create_purchase_receipt" ], "fields": [ { @@ -174,6 +178,28 @@ "fieldtype": "Float", "label": "Blanket Order Allowance (%)" }, + { + "fieldname": "section_break_xcug", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_izrr", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Subcontracting Order (Draft) will be auto-created on submission of Purchase Order.", + "fieldname": "auto_create_subcontracting_order", + "fieldtype": "Check", + "label": "Auto Create Subcontracting Order" + }, + { + "default": "0", + "description": "Purchase Receipt (Draft) will be auto-created on submission of Subcontracting Receipt.", + "fieldname": "auto_create_purchase_receipt", + "fieldtype": "Check", + "label": "Auto Create Purchase Receipt" + }, { "default": "Each Transaction", "description": "How often should Project be updated of Total Purchase Cost ?", @@ -188,7 +214,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-24 10:55:51.287327", + "modified": "2023-11-28 13:01:18.403492", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 961697c0ac..f000185393 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -8,7 +8,7 @@ import frappe from frappe import _, msgprint from frappe.desk.notifications import clear_doctype_notifications from frappe.model.mapper import get_mapped_doc -from frappe.utils import cint, cstr, flt +from frappe.utils import cint, cstr, flt, get_link_to_form from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( unlink_inter_company_doc, @@ -357,6 +357,8 @@ class PurchaseOrder(BuyingController): update_linked_doc(self.doctype, self.name, self.inter_company_order_reference) + self.auto_create_subcontracting_order() + def on_cancel(self): self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry") super(PurchaseOrder, self).on_cancel() @@ -484,6 +486,11 @@ class PurchaseOrder(BuyingController): return result + def auto_create_subcontracting_order(self): + if self.is_subcontracted and not self.is_old_subcontracting_flow: + if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): + make_subcontracting_order(self.name, save=True, notify=True) + def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0): """get last purchase rate for an item""" @@ -686,8 +693,30 @@ def make_inter_company_sales_order(source_name, target_doc=None): @frappe.whitelist() -def make_subcontracting_order(source_name, target_doc=None): - return get_mapped_subcontracting_order(source_name, target_doc) +def make_subcontracting_order( + source_name, target_doc=None, save=False, submit=False, notify=False +): + target_doc = get_mapped_subcontracting_order(source_name, target_doc) + + if (save or submit) and frappe.has_permission(target_doc.doctype, "create"): + target_doc.save() + + if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc): + try: + target_doc.submit() + except Exception as e: + target_doc.add_comment("Comment", _("Submit Action Failed") + "

" + str(e)) + + if notify: + frappe.msgprint( + _("Subcontracting Order {0} created.").format( + get_link_to_form(target_doc.doctype, target_doc.name) + ), + indicator="green", + alert=True, + ) + + return target_doc def get_mapped_subcontracting_order(source_name, target_doc=None): @@ -713,7 +742,9 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): }, "Purchase Order Item": { "doctype": "Subcontracting Order Service Item", - "field_map": {}, + "field_map": { + "name": "purchase_order_item", + }, "field_no_map": [], }, }, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py index 05b5a8e7b8..36fe079fc9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py @@ -22,7 +22,10 @@ def get_data(): "label": _("Reference"), "items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"], }, - {"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]}, + { + "label": _("Sub-contracting"), + "items": ["Subcontracting Order", "Subcontracting Receipt", "Stock Entry"], + }, {"label": _("Internal"), "items": ["Sales Order"]}, ], } diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 0f8574c84d..f80a00a95f 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -981,6 +981,38 @@ class TestPurchaseOrder(FrappeTestCase): self.assertEqual(po.items[0].qty, 30) self.assertEqual(po.items[0].fg_item_qty, 30) + @change_settings("Buying Settings", {"auto_create_subcontracting_order": 1}) + def test_auto_create_subcontracting_order(self): + from erpnext.controllers.tests.test_subcontracting_controller import ( + make_bom_for_subcontracted_items, + make_raw_materials, + make_service_items, + make_subcontracted_items, + ) + + make_subcontracted_items() + make_raw_materials() + make_service_items() + make_bom_for_subcontracted_items() + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 7", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA7", + "fg_item_qty": 10, + }, + ] + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse="_Test Warehouse 1 - _TC", + ) + + self.assertTrue(frappe.db.get_value("Subcontracting Order", {"purchase_order": po.name})) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index e91212b031..8e3a15a0ad 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -391,7 +391,10 @@ def make_return_doc( if doc.get("discount_amount"): doc.discount_amount = -1 * source.discount_amount - if doctype != "Subcontracting Receipt": + if doctype == "Subcontracting Receipt": + doc.set_warehouse = source.set_warehouse + doc.supplier_warehouse = source.supplier_warehouse + else: doc.run_method("calculate_taxes_and_totals") def update_item(source_doc, target_doc, source_parent): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 6552cd7fce..6c9d3392e3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -49,6 +49,14 @@ frappe.ui.form.on("Purchase Receipt", { } }); + frm.set_query("subcontracting_receipt", function() { + return { + filters: { + 'docstatus': 1, + 'supplier': frm.doc.supplier, + } + } + }); }, onload: function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { @@ -114,6 +122,20 @@ frappe.ui.form.on("Purchase Receipt", { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, + subcontracting_receipt: (frm) => { + if (frm.doc.is_subcontracted === 1 && frm.doc.is_old_subcontracting_flow === 0 && frm.doc.subcontracting_receipt) { + frm.set_value('items', null); + + erpnext.utils.map_current_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_purchase_receipt', + source_name: frm.doc.subcontracting_receipt, + target_doc: frm, + freeze: true, + freeze_message: __('Mapping Purchase Receipt ...'), + }); + } + }, + toggle_display_account_head: function(frm) { var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company) frm.fields_dict["items"].grid.set_column_disp(["cost_center"], enabled); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index c8a9e3e82e..c7ad660497 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -16,6 +16,7 @@ "supplier", "supplier_name", "supplier_delivery_note", + "subcontracting_receipt", "column_break1", "posting_date", "posting_time", @@ -1236,13 +1237,21 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "depends_on": "eval: (doc.is_subcontracted && !doc.is_old_subcontracting_flow)", + "fieldname": "subcontracting_receipt", + "fieldtype": "Link", + "label": "Subcontracting Receipt", + "options": "Subcontracting Receipt", + "search_index": 1 } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-10-01 21:00:44.556816", + "modified": "2023-11-28 13:14:15.243474", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index ce2e5d7f84..a86e63daf4 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -127,7 +127,8 @@ "section_break_80", "page_break", "sales_order", - "sales_order_item" + "sales_order_item", + "subcontracting_receipt_item" ], "fields": [ { @@ -1086,12 +1087,23 @@ "print_hide": 1, "read_only": 1, "search_index": 1 + }, + { + "fieldname": "subcontracting_receipt_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Subcontracting Receipt Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "search_index": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:38:15.251994", + "modified": "2023-11-28 13:37:29.245204", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 70ca1c31f5..6a658460da 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -159,6 +159,7 @@ class SubcontractingOrder(SubcontractingController): ) or item.default_bom ) + items.append( { "item_code": item.item_code, @@ -168,7 +169,8 @@ class SubcontractingOrder(SubcontractingController): "qty": si.fg_item_qty, "stock_uom": item.stock_uom, "bom": bom, - }, + "purchase_order_item": si.purchase_order_item, + } ) else: frappe.throw( @@ -176,11 +178,12 @@ class SubcontractingOrder(SubcontractingController): si.item_name or si.item_code ) ) - else: + + if items: for item in items: self.append("items", item) - else: - self.set_missing_values() + + self.set_missing_values() def update_status(self, status=None, update_modified=True): if self.docstatus >= 1 and not status: @@ -222,9 +225,11 @@ def make_subcontracting_receipt(source_name, target_doc=None): def get_mapped_subcontracting_receipt(source_name, target_doc=None): - def update_item(obj, target, source_parent): - target.qty = flt(obj.qty) - flt(obj.received_qty) - target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) + def update_item(source, target, source_parent): + target.purchase_order = source_parent.purchase_order + target.purchase_order_item = source.purchase_order_item + target.qty = flt(source.qty) - flt(source.received_qty) + target.amount = (flt(source.qty) - flt(source.received_qty)) * flt(source.rate) target_doc = get_mapped_doc( "Subcontracting Order", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index 46c229bfd3..911e903d23 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -45,7 +45,8 @@ "dimension_col_break", "project", "section_break_34", - "page_break" + "page_break", + "purchase_order_item" ], "fields": [ { @@ -332,13 +333,22 @@ "fieldtype": "Link", "label": "Project", "options": "Project" + }, + { + "fieldname": "purchase_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Order Item", + "no_copy": 1, + "read_only": 1, + "search_index": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:38:37.640677", + "modified": "2023-11-23 16:56:22.182698", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json index f213313ef6..dc18543b04 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json @@ -1,131 +1,141 @@ { - "actions": [], - "autoname": "hash", - "creation": "2022-04-01 19:23:05.728354", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "column_break_2", - "item_name", - "section_break_4", - "qty", - "column_break_6", - "rate", - "column_break_8", - "amount", - "section_break_10", - "fg_item", - "column_break_12", - "fg_item_qty" - ], - "fields": [ - { - "bold": 1, - "columns": 2, - "fieldname": "item_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Code", - "options": "Item", - "reqd": 1, - "search_index": 1 - }, - { - "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Data", - "in_global_search": 1, - "in_list_view": 1, - "label": "Item Name", - "print_hide": 1, - "reqd": 1 - }, - { - "bold": 1, - "columns": 1, - "fieldname": "qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Quantity", - "print_width": "60px", - "reqd": 1, - "width": "60px" - }, - { - "bold": 1, - "columns": 2, - "fetch_from": "item_code.standard_rate", - "fetch_if_empty": 1, - "fieldname": "rate", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Rate", - "options": "currency", - "reqd": 1 - }, - { - "columns": 2, - "fieldname": "amount", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Amount", - "options": "currency", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "fg_item", - "fieldtype": "Link", - "label": "Finished Good Item", - "options": "Item", - "reqd": 1 - }, - { - "default": "1", - "fieldname": "fg_item_qty", - "fieldtype": "Float", - "label": "Finished Good Item Quantity", - "reqd": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_10", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - } - ], - "istable": 1, - "links": [], - "modified": "2022-04-07 11:43:43.094867", - "modified_by": "Administrator", - "module": "Subcontracting", - "name": "Subcontracting Order Service Item", - "naming_rule": "Random", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "search_fields": "item_name", - "sort_field": "modified", - "sort_order": "DESC", - "states": [] + "actions": [], + "autoname": "hash", + "creation": "2022-04-01 19:23:05.728354", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "column_break_2", + "item_name", + "section_break_4", + "qty", + "column_break_6", + "rate", + "column_break_8", + "amount", + "section_break_10", + "fg_item", + "column_break_12", + "fg_item_qty", + "purchase_order_item" + ], + "fields": [ + { + "bold": 1, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Name", + "print_hide": 1, + "reqd": 1 + }, + { + "bold": 1, + "columns": 1, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "print_width": "60px", + "reqd": 1, + "width": "60px" + }, + { + "bold": 1, + "columns": 2, + "fetch_from": "item_code.standard_rate", + "fetch_if_empty": 1, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "fg_item", + "fieldtype": "Link", + "label": "Finished Good Item", + "options": "Item", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "fg_item_qty", + "fieldtype": "Float", + "label": "Finished Good Item Quantity", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Order Item", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2023-11-23 17:05:04.561948", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order Service Item", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "item_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 36001eb78f..575c4eda73 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -11,6 +11,10 @@ frappe.ui.form.on('Subcontracting Receipt', { frm.get_field('supplied_items').grid.cannot_add_rows = true; frm.get_field('supplied_items').grid.only_sortable(); frm.trigger('set_queries'); + + frm.custom_make_buttons = { + 'Purchase Receipt': 'Purchase Receipt', + } }, on_submit(frm) { @@ -24,64 +28,75 @@ frappe.ui.form.on('Subcontracting Receipt', { }, refresh: (frm) => { - if (frm.doc.docstatus > 0) { + if (frm.doc.docstatus === 1) { frm.add_custom_button(__('Stock Ledger'), () => { - frappe.route_options = { - voucher_no: frm.doc.name, - from_date: frm.doc.posting_date, - to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), - company: frm.doc.company, - show_cancelled_entries: frm.doc.docstatus === 2 - } - frappe.set_route('query-report', 'Stock Ledger'); - }, __('View')); + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + company: frm.doc.company, + show_cancelled_entries: frm.doc.docstatus === 2 + } + frappe.set_route('query-report', 'Stock Ledger'); + }, __('View')); frm.add_custom_button(__('Accounting Ledger'), () => { - frappe.route_options = { - voucher_no: frm.doc.name, - from_date: frm.doc.posting_date, - to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), - company: frm.doc.company, - group_by: 'Group by Voucher (Consolidated)', - show_cancelled_entries: frm.doc.docstatus === 2 - } - frappe.set_route('query-report', 'General Ledger'); - }, __('View')); + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + company: frm.doc.company, + group_by: 'Group by Voucher (Consolidated)', + show_cancelled_entries: frm.doc.docstatus === 2 + } + frappe.set_route('query-report', 'General Ledger'); + }, __('View')); + + if (frm.doc.is_return === 0) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frappe.model.open_mapped_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_purchase_receipt', + frm: frm, + freeze: true, + freeze_message: __('Creating Purchase Receipt ...') + }); + }, __('Create')); + } } if (!frm.doc.is_return && frm.doc.docstatus === 1 && frm.doc.per_returned < 100) { frm.add_custom_button(__('Subcontract Return'), () => { - frappe.model.open_mapped_doc({ - method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return', - frm: frm - }); - }, __('Create')); + frappe.model.open_mapped_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return', + frm: frm + }); + }, __('Create')); frm.page.set_inner_btn_group_as_primary(__('Create')); } if (frm.doc.docstatus === 0) { frm.add_custom_button(__('Subcontracting Order'), () => { - if (!frm.doc.supplier) { - frappe.throw({ - title: __('Mandatory'), - message: __('Please Select a Supplier') - }); - } - - erpnext.utils.map_current_doc({ - method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt', - source_doctype: 'Subcontracting Order', - target: frm, - setters: { - supplier: frm.doc.supplier, - }, - get_query_filters: { - docstatus: 1, - per_received: ['<', 100], - company: frm.doc.company - } + if (!frm.doc.supplier) { + frappe.throw({ + title: __('Mandatory'), + message: __('Please Select a Supplier') }); - }, __('Get Items From')); + } + + erpnext.utils.map_current_doc({ + method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt', + source_doctype: 'Subcontracting Order', + target: frm, + setters: { + supplier: frm.doc.supplier, + }, + get_query_filters: { + docstatus: 1, + per_received: ['<', 100], + company: frm.doc.company + } + }); + }, __('Get Items From')); frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM'); } diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index ae64cc632a..6df9129700 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -3,7 +3,8 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, getdate, nowdate +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate import erpnext from erpnext.accounts.utils import get_account_currency @@ -80,6 +81,7 @@ class SubcontractingReceipt(SubcontractingController): self.make_gl_entries() self.repost_future_sle_and_gle() self.update_status() + self.auto_create_purchase_receipt() def on_update(self): for table_field in ["items", "supplied_items"]: @@ -528,9 +530,102 @@ class SubcontractingReceipt(SubcontractingController): + "\n".join(warehouse_with_no_account) ) + def auto_create_purchase_receipt(self): + if frappe.db.get_single_value("Buying Settings", "auto_create_purchase_receipt"): + make_purchase_receipt(self, save=True, notify=True) + @frappe.whitelist() def make_subcontract_return(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc return make_return_doc("Subcontracting Receipt", source_name, target_doc) + + +@frappe.whitelist() +def make_purchase_receipt(source_name, target_doc=None, save=False, submit=False, notify=False): + if isinstance(source_name, str): + source_doc = frappe.get_doc("Subcontracting Receipt", source_name) + else: + source_doc = source_name + + if not source_doc.is_return: + if not target_doc: + target_doc = frappe.new_doc("Purchase Receipt") + target_doc.is_subcontracted = 1 + target_doc.is_old_subcontracting_flow = 0 + + target_doc = get_mapped_doc( + "Subcontracting Receipt", + source_doc.name, + { + "Subcontracting Receipt": { + "doctype": "Purchase Receipt", + "field_map": { + "posting_date": "posting_date", + "posting_time": "posting_time", + "name": "subcontracting_receipt", + "supplier_warehouse": "supplier_warehouse", + }, + "field_no_map": ["total_qty", "total"], + }, + }, + target_doc, + ignore_child_tables=True, + ) + + target_doc.currency = frappe.get_cached_value("Company", target_doc.company, "default_currency") + + po_items_details = {} + for item in source_doc.items: + if item.purchase_order and item.purchase_order_item: + if item.purchase_order not in po_items_details: + po_doc = frappe.get_doc("Purchase Order", item.purchase_order) + po_items_details[item.purchase_order] = {po_item.name: po_item for po_item in po_doc.items} + + if po_item := po_items_details[item.purchase_order].get(item.purchase_order_item): + conversion_factor = flt(po_item.qty) / flt(po_item.fg_item_qty) + item_row = { + "item_code": po_item.item_code, + "item_name": po_item.item_name, + "conversion_factor": conversion_factor, + "qty": flt(item.qty) * conversion_factor, + "rejected_qty": flt(item.rejected_qty) * conversion_factor, + "uom": po_item.uom, + "rate": po_item.rate, + "warehouse": item.warehouse, + "rejected_warehouse": item.rejected_warehouse, + "purchase_order": item.purchase_order, + "purchase_order_item": item.purchase_order_item, + "subcontracting_receipt_item": item.name, + } + target_doc.append("items", item_row) + + if not target_doc.items: + frappe.throw( + _("Purchase Order Item reference is missing in Subcontracting Receipt {0}").format( + source_doc.name + ) + ) + + target_doc.set_missing_values() + + if (save or submit) and frappe.has_permission(target_doc.doctype, "create"): + target_doc.save() + + if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc): + try: + target_doc.submit() + except Exception as e: + target_doc.add_comment("Comment", _("Submit Action Failed") + "

" + str(e)) + + if notify: + frappe.msgprint( + _("Purchase Receipt {0} created.").format( + get_link_to_form(target_doc.doctype, target_doc.name) + ), + indicator="green", + alert=True, + ) + + return target_doc diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py index deb8342b83..6b67a1621a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py @@ -3,17 +3,27 @@ from frappe import _ def get_data(): return { - "fieldname": "subcontracting_receipt_no", + "fieldname": "subcontracting_receipt", "non_standard_fieldnames": { "Subcontracting Receipt": "return_against", }, "internal_links": { "Subcontracting Order": ["items", "subcontracting_order"], + "Purchase Order": ["items", "purchase_order"], "Project": ["items", "project"], "Quality Inspection": ["items", "quality_inspection"], }, "transactions": [ - {"label": _("Reference"), "items": ["Subcontracting Order", "Quality Inspection", "Project"]}, + { + "label": _("Reference"), + "items": [ + "Purchase Order", + "Purchase Receipt", + "Subcontracting Order", + "Quality Inspection", + "Project", + ], + }, {"label": _("Returns"), "items": ["Subcontracting Receipt"]}, ], } diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index f0e4e00074..1d007fe3c8 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -5,7 +5,7 @@ import copy import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, nowtime, today import erpnext @@ -953,6 +953,33 @@ class TestSubcontractingReceipt(FrappeTestCase): scr.submit() + @change_settings("Buying Settings", {"auto_create_purchase_receipt": 1}) + def test_auto_create_purchase_receipt(self): + fg_item = "Subcontracted Item SA1" + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 5, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + + self.assertTrue(frappe.db.get_value("Purchase Receipt", {"subcontracting_receipt": scr.name})) + def make_return_subcontracting_receipt(**args): args = frappe._dict(args) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 26a29dd811..35a79a9308 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -63,7 +63,9 @@ "dimension_col_break", "project", "section_break_80", - "page_break" + "page_break", + "purchase_order", + "purchase_order_item" ], "fields": [ { @@ -517,12 +519,31 @@ "label": "Reference Name", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "purchase_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Order Item", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "hidden": 1, + "label": "Purchase Order", + "no_copy": 1, + "options": "Purchase Order", + "read_only": 1, + "search_index": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:38:26.459669", + "modified": "2023-11-23 17:38:55.134685", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item",