From 70a1f4062497818ef4ea1d6bc44e682af33f7038 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 21 Apr 2022 20:28:06 +0530 Subject: [PATCH] feat: New DocType "Subcontracting Receipt" --- .../controllers/sales_and_purchase_return.py | 9 +- erpnext/stock/doctype/serial_no/serial_no.py | 11 +- .../subcontracting_receipt/__init__.py | 0 .../subcontracting_receipt.js | 157 +++++ .../subcontracting_receipt.json | 645 ++++++++++++++++++ .../subcontracting_receipt.py | 197 ++++++ .../subcontracting_receipt_dashboard.py | 15 + .../subcontracting_receipt_list.js | 14 + .../test_subcontracting_receipt.py | 9 + 9 files changed, 1052 insertions(+), 5 deletions(-) create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/__init__.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js create mode 100644 erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index bdde3a1fd8c..9642c24a9e0 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -343,7 +343,7 @@ def make_return_doc(doctype, source_name, target_doc=None): # look for Print Heading "Debit Note" doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note")) - for tax in doc.get("taxes"): + for tax in doc.get("taxes") or []: if tax.charge_type == "Actual": tax.tax_amount = -1 * tax.tax_amount @@ -382,8 +382,11 @@ def make_return_doc(doctype, source_name, target_doc=None): for d in doc.get("packed_items"): d.qty = d.qty * -1 - doc.discount_amount = -1 * source.discount_amount - doc.run_method("calculate_taxes_and_totals") + if doc.get("discount_amount"): + doc.discount_amount = -1 * source.discount_amount + + if doctype != "Subcontracting Receipt": + doc.run_method("calculate_taxes_and_totals") def update_item(source_doc, target_doc, source_parent): target_doc.qty = -1 * source_doc.qty diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 316c897da02..c302454c664 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -687,7 +687,10 @@ def update_serial_nos_after_submit(controller, parentfield): update_rejected_serial_nos = ( True - if (controller.doctype in ("Purchase Receipt", "Purchase Invoice") and d.rejected_qty) + if ( + controller.doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt") + and d.rejected_qty + ) else False ) accepted_serial_nos_updated = False @@ -700,7 +703,11 @@ def update_serial_nos_after_submit(controller, parentfield): qty = d.stock_qty else: warehouse = d.warehouse - qty = d.qty if controller.doctype == "Stock Reconciliation" else d.stock_qty + qty = ( + d.qty + if controller.doctype in ["Stock Reconciliation", "Subcontracting Receipt"] + else d.stock_qty + ) for sle in stock_ledger_entries: if sle.voucher_detail_no == d.name: if ( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/__init__.py b/erpnext/subcontracting/doctype/subcontracting_receipt/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js new file mode 100644 index 00000000000..b98f979c668 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -0,0 +1,157 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.provide('erpnext.buying'); + +frappe.ui.form.on('Subcontracting Receipt', { + setup: (frm) => { + frm.get_field('supplied_items').grid.cannot_add_rows = true; + frm.get_field('supplied_items').grid.only_sortable(); + + frm.set_query('set_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('rejected_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('supplier_warehouse', () => { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + + frm.set_query('warehouse', 'items', () => ({ + filters: { + company: frm.doc.company, + is_group: 0 + } + })); + + frm.set_query('rejected_warehouse', 'items', () => ({ + filters: { + company: frm.doc.company, + is_group: 0 + } + })); + }, + + 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")); + } + + if (!frm.doc.is_return && frm.doc.docstatus == 1) { + 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")); + } + }, + + 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('Subcontracting Receipt Item', { + item_code(frm) { + set_missing_values(frm); + }, + + qty(frm) { + set_missing_values(frm); + }, + + rate(frm) { + set_missing_values(frm); + }, +}); + +frappe.ui.form.on('Subcontracting Receipt Supplied Item', { + consumed_qty(frm) { + set_missing_values(frm); + }, +}); + +let set_warehouse_in_children = (child_table, warehouse_field, warehouse) => { + let transaction_controller = new erpnext.TransactionController(); + transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse); +} + +let set_missing_values = (frm) => { + frappe.call({ + doc: frm.doc, + method: 'set_missing_values', + callback: (r) => { + if (!r.exc) frm.refresh(); + }, + }); +}; \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json new file mode 100644 index 00000000000..e9638144a79 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -0,0 +1,645 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2022-04-18 11:20:44.226738", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "naming_series", + "supplier", + "supplier_name", + "column_break1", + "company", + "posting_date", + "posting_time", + "is_return", + "return_against", + "section_addresses", + "supplier_address", + "contact_person", + "address_display", + "contact_display", + "contact_mobile", + "contact_email", + "col_break_address", + "shipping_address", + "shipping_address_display", + "billing_address", + "billing_address_display", + "sec_warehouse", + "set_warehouse", + "rejected_warehouse", + "col_break_warehouse", + "supplier_warehouse", + "items_section", + "items", + "section_break0", + "total_qty", + "column_break_27", + "total", + "raw_material_details", + "get_current_stock", + "supplied_items", + "section_break_46", + "in_words", + "bill_no", + "bill_date", + "accounting_details_section", + "provisional_expense_account", + "more_info", + "status", + "column_break_39", + "per_returned", + "section_break_47", + "amended_from", + "range", + "column_break4", + "represents_company", + "subscription_detail", + "auto_repeat", + "printing_settings", + "letter_head", + "language", + "instructions", + "column_break_97", + "select_print_heading", + "other_details", + "remarks", + "transporter_info", + "transporter_name", + "column_break5", + "lr_no", + "lr_date" + ], + "fields": [ + { + "allow_on_submit": 1, + "default": "{supplier_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-SCR-.YYYY.-\nMAT-SCR-RET-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "supplier", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Supplier", + "options": "Supplier", + "print_hide": 1, + "print_width": "150px", + "reqd": 1, + "search_index": 1, + "width": "150px" + }, + { + "bold": 1, + "depends_on": "supplier", + "fetch_from": "supplier.supplier_name", + "fieldname": "supplier_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Supplier Name", + "read_only": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "no_copy": 1, + "print_width": "100px", + "reqd": 1, + "search_index": 1, + "width": "100px" + }, + { + "description": "Time at which materials were received", + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "print_hide": 1, + "print_width": "100px", + "reqd": 1, + "width": "100px" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "print_width": "150px", + "remember_last_selected_value": 1, + "reqd": 1, + "width": "150px" + }, + { + "collapsible": 1, + "fieldname": "section_addresses", + "fieldtype": "Section Break", + "label": "Address and Contact" + }, + { + "fieldname": "supplier_address", + "fieldtype": "Link", + "label": "Select Supplier Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Address", + "read_only": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Mobile No", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Small Text", + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "col_break_address", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Select Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Small Text", + "label": "Shipping Address", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "sec_warehouse", + "fieldtype": "Section Break" + }, + { + "description": "Sets 'Accepted Warehouse' in each row of the Items table.", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Accepted Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, + { + "description": "Sets 'Rejected Warehouse' in each row of the Items table.", + "fieldname": "rejected_warehouse", + "fieldtype": "Link", + "label": "Rejected Warehouse", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1 + }, + { + "fieldname": "col_break_warehouse", + "fieldtype": "Column Break" + }, + { + "fieldname": "supplier_warehouse", + "fieldtype": "Link", + "label": "Supplier Warehouse", + "no_copy": 1, + "options": "Warehouse", + "print_hide": 1, + "print_width": "50px", + "width": "50px" + }, + { + "fieldname": "items_section", + "fieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "allow_bulk_edit": 1, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Subcontracting Receipt Item", + "reqd": 1 + }, + { + "depends_on": "supplied_items", + "fieldname": "get_current_stock", + "fieldtype": "Button", + "label": "Get Current Stock", + "options": "get_current_stock", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "supplied_items", + "depends_on": "supplied_items", + "fieldname": "raw_material_details", + "fieldtype": "Section Break", + "label": "Raw Materials Consumed", + "options": "fa fa-table", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "supplied_items", + "fieldtype": "Table", + "label": "Consumed Items", + "no_copy": 1, + "options": "Subcontracting Receipt Supplied Item", + "print_hide": 1 + }, + { + "fieldname": "section_break0", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "section_break_46", + "fieldtype": "Section Break" + }, + { + "fieldname": "in_words", + "fieldtype": "Data", + "label": "In Words", + "length": 240, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "bill_no", + "fieldtype": "Data", + "hidden": 1, + "label": "Bill No", + "print_hide": 1 + }, + { + "fieldname": "bill_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Bill Date", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "options": "fa fa-file-text" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "reqd": 1, + "search_index": 1, + "width": "150px" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Subcontracting Receipt", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "range", + "fieldtype": "Data", + "hidden": 1, + "label": "Range", + "print_hide": 1 + }, + { + "fieldname": "column_break4", + "fieldtype": "Column Break", + "print_hide": 1, + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "subscription_detail", + "fieldtype": "Section Break", + "label": "Auto Repeat Detail" + }, + { + "fieldname": "auto_repeat", + "fieldtype": "Link", + "label": "Auto Repeat", + "no_copy": 1, + "options": "Auto Repeat", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "printing_settings", + "fieldtype": "Section Break", + "label": "Printing Settings" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "language", + "fieldtype": "Data", + "label": "Print Language", + "read_only": 1 + }, + { + "fieldname": "column_break_97", + "fieldtype": "Column Break" + }, + { + "fieldname": "other_details", + "fieldtype": "HTML", + "hidden": 1, + "label": "Other Details", + "options": "
Other Details
", + "print_hide": 1, + "print_width": "30%", + "width": "30%" + }, + { + "fieldname": "instructions", + "fieldtype": "Small Text", + "label": "Instructions" + }, + { + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "transporter_name", + "fieldname": "transporter_info", + "fieldtype": "Section Break", + "label": "Transporter Details", + "options": "fa fa-truck" + }, + { + "fieldname": "transporter_name", + "fieldtype": "Data", + "label": "Transporter Name" + }, + { + "fieldname": "column_break5", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "lr_no", + "fieldtype": "Data", + "label": "Vehicle Number", + "no_copy": 1, + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "lr_date", + "fieldtype": "Date", + "label": "Vehicle Date", + "no_copy": 1, + "print_width": "100px", + "width": "100px" + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Select Billing Address", + "options": "Address" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address", + "read_only": 1 + }, + { + "fetch_from": "supplier.represents_company", + "fieldname": "represents_company", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Represents Company", + "options": "Company", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "provisional_expense_account", + "fieldtype": "Link", + "hidden": 1, + "label": "Provisional Expense Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "is_return", + "fieldtype": "Check", + "label": "Is Return", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "is_return", + "fieldname": "return_against", + "fieldtype": "Link", + "label": "Return Against Subcontracting Receipt", + "no_copy": 1, + "options": "Subcontracting Receipt", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:(!doc.__islocal && doc.is_return==0)", + "fieldname": "per_returned", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Returned", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_47", + "fieldtype": "Section Break" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2022-04-18 13:15:12.011682", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Receipt", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "read": 1, + "report": 1, + "role": "Accounts User" + }, + { + "permlevel": 1, + "read": 1, + "role": "Stock Manager", + "write": 1 + } + ], + "search_fields": "status, posting_date, supplier", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "timeline_field": "supplier", + "title_field": "title", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py new file mode 100644 index 00000000000..d80bbdf453a --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -0,0 +1,197 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import cint, flt, getdate, nowdate + +from erpnext.controllers.subcontracting_controller import SubcontractingController + + +class SubcontractingReceipt(SubcontractingController): + def __init__(self, *args, **kwargs): + super(SubcontractingReceipt, self).__init__(*args, **kwargs) + self.status_updater = [ + { + "target_dt": "Subcontracting Order Item", + "join_field": "subcontracting_order_item", + "target_field": "received_qty", + "target_parent_dt": "Subcontracting Order", + "target_parent_field": "per_received", + "target_ref_field": "qty", + "source_dt": "Subcontracting Receipt Item", + "source_field": "received_qty", + "percent_join_field": "subcontracting_order", + "overflow_type": "receipt", + }, + ] + + def update_status_updater_args(self): + if cint(self.is_return): + self.status_updater.extend( + [ + { + "source_dt": "Subcontracting Receipt Item", + "target_dt": "Subcontracting Order Item", + "join_field": "subcontracting_order_item", + "target_field": "returned_qty", + "source_field": "-1 * qty", + "extra_cond": """ and exists (select name from `tabSubcontracting Receipt` + where name=`tabSubcontracting Receipt Item`.parent and is_return=1)""", + }, + { + "source_dt": "Subcontracting Receipt Item", + "target_dt": "Subcontracting Receipt Item", + "join_field": "subcontracting_receipt_item", + "target_field": "returned_qty", + "target_parent_dt": "Subcontracting Receipt", + "target_parent_field": "per_returned", + "target_ref_field": "received_qty", + "source_field": "-1 * received_qty", + "percent_join_field_parent": "return_against", + }, + ] + ) + + def before_validate(self): + super(SubcontractingReceipt, self).before_validate() + self.set_items_cost_center() + self.set_items_expense_account() + + def validate(self): + super(SubcontractingReceipt, self).validate() + self.set_missing_values() + self.validate_posting_time() + self.validate_rejected_warehouse() + + if self._action == "submit": + self.make_batches("warehouse") + + if getdate(self.posting_date) > getdate(nowdate()): + frappe.throw(_("Posting Date cannot be future date")) + + self.reset_default_field_value("set_warehouse", "items", "warehouse") + self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") + self.get_current_stock() + + def on_submit(self): + self.update_status_updater_args() + self.update_prevdoc_status() + self.set_subcontracting_order_status() + self.set_consumed_qty_in_sco() + self.update_stock_ledger() + + from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit + + update_serial_nos_after_submit(self, "items") + + self.make_gl_entries() + self.repost_future_sle_and_gle() + self.update_status() + + def on_cancel(self): + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") + self.update_status_updater_args() + self.update_prevdoc_status() + self.update_stock_ledger() + self.make_gl_entries_on_cancel() + self.repost_future_sle_and_gle() + self.delete_auto_created_batches() + self.set_consumed_qty_in_sco() + self.set_subcontracting_order_status() + self.update_status() + + @frappe.whitelist() + def set_missing_values(self): + self.set_missing_values_in_supplied_items() + self.set_missing_values_in_items() + + def set_missing_values_in_supplied_items(self): + for item in self.get("supplied_items") or []: + item.amount = item.rate * item.consumed_qty + + def set_missing_values_in_items(self): + rm_supp_cost = {} + for item in self.get("supplied_items") or []: + if item.reference_name in rm_supp_cost: + rm_supp_cost[item.reference_name] += item.amount + else: + rm_supp_cost[item.reference_name] = item.amount + + total_qty = total_amount = 0 + for item in self.items: + if item.name in rm_supp_cost: + item.rm_supp_cost = rm_supp_cost[item.name] + item.rm_cost_per_qty = item.rm_supp_cost / item.qty + rm_supp_cost.pop(item.name) + + if self.is_new() and item.rm_supp_cost > 0: + item.rate = item.rm_cost_per_qty + (item.service_cost_per_qty or 0) + item.additional_cost_per_qty + + item.received_qty = item.qty + (item.rejected_qty or 0) + item.amount = item.qty * item.rate + total_qty += item.qty + total_amount += item.amount + else: + self.total_qty = total_qty + self.total = total_amount + + def validate_rejected_warehouse(self): + if not self.rejected_warehouse: + for item in self.items: + if item.rejected_qty: + frappe.throw( + _("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code) + ) + + def set_items_cost_center(self): + if self.company: + cost_center = frappe.get_cached_value("Company", self.company, "cost_center") + + for item in self.items: + if not item.cost_center: + item.cost_center = cost_center + + def set_items_expense_account(self): + if self.company: + expense_account = self.get_company_default("default_expense_account", ignore_validation=True) + + for item in self.items: + if not item.expense_account: + item.expense_account = expense_account + + @frappe.whitelist() + def get_current_stock(self): + for item in self.get("supplied_items"): + if self.supplier_warehouse: + actual_qty = frappe.db.get_value( + "Bin", + {"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse}, + "actual_qty", + ) + item.current_stock = flt(actual_qty) or 0 + + def update_status(self, status=None, update_modified=False): + if self.docstatus >= 1 and not status: + if self.docstatus == 1: + if self.is_return: + status = "Return" + return_against = frappe.get_doc("Subcontracting Receipt", self.return_against) + return_against.run_method("update_status") + else: + if self.per_returned == 100: + status = "Return Issued" + elif self.status == "Draft": + status = "Completed" + elif self.docstatus == 2: + status = "Cancelled" + + if status: + frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified) + + +@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) \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py new file mode 100644 index 00000000000..a9e51937d7b --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_dashboard.py @@ -0,0 +1,15 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "subcontracting_receipt_no", + "internal_links": { + "Subcontracting Order": ["items", "subcontracting_order"], + "Project": ["items", "project"], + "Quality Inspection": ["items", "quality_inspection"], + }, + "transactions": [ + {"label": _("Reference"), "items": ["Subcontracting Order", "Quality Inspection", "Project"]}, + ], + } diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js new file mode 100644 index 00000000000..6d961de8c64 --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js @@ -0,0 +1,14 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.listview_settings['Subcontracting Receipt'] = { + get_indicator: function (doc) { + const status_colors = { + "Draft": "grey", + "Return": "gray", + "Return Issued": "grey", + "Completed": "green", + }; + return [__(doc.status), status_colors[doc.status], "status,=," + doc.status]; + }, +}; \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py new file mode 100644 index 00000000000..bc41dca319f --- /dev/null +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestSubcontractingReceipt(FrappeTestCase): + pass