diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d669abe910..ae54b801f1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -599,6 +599,7 @@ class StockController(AccountsController): inspection_fieldname_map = { "Purchase Receipt": "inspection_required_before_purchase", "Purchase Invoice": "inspection_required_before_purchase", + "Subcontracting Receipt": "inspection_required_before_purchase", "Sales Invoice": "inspection_required_before_delivery", "Delivery Note": "inspection_required_before_delivery", } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 59d2b154ac..ac5735b707 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -277,7 +277,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } setup_quality_inspection() { - if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) { + if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) { return; } @@ -289,7 +289,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.page.set_inner_btn_group_as_primary(__('Create')); } - const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) + const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype) ? "Incoming" : "Outgoing"; let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); @@ -2067,6 +2067,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe const me = this; const dialog = new frappe.ui.Dialog({ title: __("Select Items for Quality Inspection"), + size: "extra-large", fields: fields, primary_action: function () { const data = dialog.get_values(); diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index db9322f326..914a9f3c21 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -74,7 +74,7 @@ "fieldname": "reference_type", "fieldtype": "Select", "label": "Reference Type", - "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry\nJob Card", + "options": "\nPurchase Receipt\nPurchase Invoice\nSubcontracting Receipt\nDelivery Note\nSales Invoice\nStock Entry\nJob Card", "reqd": 1 }, { @@ -245,7 +245,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-10-04 22:00:13.995221", + "modified": "2023-08-23 11:56:50.282878", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 94a2589b98..e374077a78 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -3,14 +3,91 @@ frappe.provide('erpnext.buying'); -erpnext.landed_cost_taxes_and_charges.setup_triggers("Subcontracting Receipt"); +erpnext.landed_cost_taxes_and_charges.setup_triggers('Subcontracting Receipt'); frappe.ui.form.on('Subcontracting Receipt', { setup: (frm) => { frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle']; frm.get_field('supplied_items').grid.cannot_add_rows = true; frm.get_field('supplied_items').grid.only_sortable(); + frm.trigger('set_queries'); + }, + refresh: (frm) => { + if (frm.doc.docstatus > 0) { + 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')); + + 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')); + } + + 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')); + 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 + } + }); + }, __('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'); + } + + frm.trigger('setup_quality_inspection'); + }, + + set_warehouse: (frm) => { + set_warehouse_in_children(frm.doc.items, 'warehouse', frm.doc.set_warehouse); + }, + + rejected_warehouse: (frm) => { + set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse); + }, + + set_queries: (frm) => { frm.set_query('set_warehouse', () => { return { filters: { @@ -52,38 +129,36 @@ frappe.ui.form.on('Subcontracting Receipt', { } })); - frm.set_query('expense_account', 'items', function () { - return { + frm.set_query('expense_account', 'items', () => ({ query: 'erpnext.controllers.queries.get_expense_account', filters: { 'company': frm.doc.company } - }; - }); + })); - frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { + frm.set_query('batch_no', 'items', (doc, cdt, cdn) => { var row = locals[cdt][cdn]; return { filters: { item: row.item_code } - } + }; }); - frm.set_query('batch_no', 'supplied_items', function(doc, cdt, cdn) { + frm.set_query('batch_no', 'supplied_items', (doc, cdt, cdn) => { var row = locals[cdt][cdn]; return { filters: { item: row.rm_item_code } - } + }; }); - frm.set_query("serial_and_batch_bundle", "supplied_items", (doc, cdt, cdn) => { + frm.set_query('serial_and_batch_bundle', 'supplied_items', (doc, cdt, cdn) => { let row = locals[cdt][cdn]; return { filters: { 'item_code': row.rm_item_code, 'voucher_type': doc.doctype, - 'voucher_no': ["in", [doc.name, ""]], + 'voucher_no': ['in', [doc.name, '']], 'is_cancelled': 0, } } @@ -101,7 +176,7 @@ frappe.ui.form.on('Subcontracting Receipt', { let batch_no_field = frm.get_docfield('items', 'batch_no'); if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { + batch_no_field.get_route_options_for_new_doc = (row) => { return { 'item': row.doc.item_code } @@ -109,85 +184,20 @@ frappe.ui.form.on('Subcontracting Receipt', { } }, - refresh: (frm) => { - if (frm.doc.docstatus > 0) { - frm.add_custom_button(__('Stock Ledger'), function () { - 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'), function () { - 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')); + setup_quality_inspection: (frm) => { + if (!frm.is_new() && frm.doc.docstatus === 0 && !frm.doc.is_return) { + let transaction_controller = new erpnext.TransactionController({ frm: frm }); + transaction_controller.setup_quality_inspection(); } - - if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) { - frm.add_custom_button(__('Subcontract Return'), function () { - 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'), function () { - 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 - } - }); - }, __('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'); - } - }, - - set_warehouse: (frm) => { - set_warehouse_in_children(frm.doc.items, 'warehouse', frm.doc.set_warehouse); - }, - - rejected_warehouse: (frm) => { - set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse); }, }); frappe.ui.form.on('Landed Cost Taxes and Charges', { - amount: function (frm, cdt, cdn) { + amount: (frm, cdt, cdn) => { frm.events.set_base_amount(frm, cdt, cdn); }, - expense_account: function (frm, cdt, cdn) { + expense_account: (frm, cdt, cdn) => { frm.events.set_account_currency(frm, cdt, cdn); } }); diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index d2bf7e8f1d..afe1b6068d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -81,6 +81,9 @@ class SubcontractingReceipt(SubcontractingController): self.validate_posting_time() self.validate_rejected_warehouse() + if not self.get("is_return"): + self.validate_inspection() + if getdate(self.posting_date) > getdate(nowdate()): frappe.throw(_("Posting Date cannot be future date")) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 887cba5b25..a170527e2d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -567,6 +567,64 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(rm_item.rate, 100) self.assertEqual(rm_item.amount, rm_item.consumed_qty * rm_item.rate) + def test_quality_inspection_for_subcontracting_receipt(self): + from erpnext.stock.doctype.quality_inspection.test_quality_inspection import ( + create_quality_inspection, + ) + + set_backflush_based_on("BOM") + 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), + ) + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + + # Enable `Inspection Required before Purchase` in Item Master + frappe.db.set_value("Item", fg_item, "inspection_required_before_purchase", 1) + + # ValidationError should be raised as Quality Inspection is not created/linked + self.assertRaises(frappe.ValidationError, scr1.submit) + + qa = create_quality_inspection( + reference_type="Subcontracting Receipt", + reference_name=scr1.name, + inspection_type="Incoming", + item_code=fg_item, + ) + scr1.reload() + self.assertEqual(scr1.items[0].quality_inspection, qa.name) + + # SCR should be submitted successfully as Quality Inspection is set + scr1.submit() + qa.cancel() + scr1.reload() + scr1.cancel() + + scr2 = make_subcontracting_receipt(sco.name) + scr2.save() + + # Disable `Inspection Required before Purchase` in Item Master + frappe.db.set_value("Item", fg_item, "inspection_required_before_purchase", 0) + + # ValidationError should not be raised as `Inspection Required before Purchase` is disabled + scr2.submit() + def make_return_subcontracting_receipt(**args): args = frappe._dict(args)