Merge branch 'develop' into add_index_to_payment_ledger
This commit is contained in:
commit
9b40f70ac2
@ -18,6 +18,7 @@
|
||||
"automatically_fetch_payment_terms",
|
||||
"column_break_17",
|
||||
"enable_common_party_accounting",
|
||||
"allow_multi_currency_invoices_against_single_party_account",
|
||||
"report_setting_section",
|
||||
"use_custom_cash_flow",
|
||||
"deferred_accounting_settings_section",
|
||||
@ -339,6 +340,13 @@
|
||||
"fieldname": "report_setting_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Report Setting"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
|
||||
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow multi-currency invoices against single party account "
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -346,7 +354,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-08 14:45:06.796418",
|
||||
"modified": "2022-07-11 13:37:50.605141",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -1 +0,0 @@
|
||||
C Form (India specific only) - Will be deprecated.
|
@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
//c-form js file
|
||||
// -----------------------------
|
||||
|
||||
frappe.ui.form.on('C-Form', {
|
||||
setup(frm) {
|
||||
frm.fields_dict.invoices.grid.get_field("invoice_no").get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": 1,
|
||||
"customer": doc.customer,
|
||||
"company": doc.company,
|
||||
"c_form_applicable": 'Yes',
|
||||
"c_form_no": ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
frm.fields_dict.state.get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
country: "India"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('C-Form Invoice Detail', {
|
||||
invoice_no(frm, cdt, cdn) {
|
||||
let d = frappe.get_doc(cdt, cdn);
|
||||
|
||||
if (d.invoice_no) {
|
||||
frm.call('get_invoice_details', {
|
||||
invoice_no: d.invoice_no
|
||||
}).then(r => {
|
||||
frappe.model.set_value(cdt, cdn, r.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -1,511 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "naming_series:",
|
||||
"beta": 0,
|
||||
"creation": "2013-03-07 11:55:06",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "ACC-CF-.YYYY.-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "c_form_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "C-Form No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "received_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Received Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Customer",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Customer",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "50%",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "quarter",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Quarter",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nI\nII\nIII\nIV",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "state",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "State",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break0",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "invoices",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoices",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "C-Form Invoice Detail",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "total_invoiced_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Invoiced Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "C-Form",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 3,
|
||||
"modified": "2018-08-21 14:44:30.558767",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "C-Form",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 1,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "DESC",
|
||||
"timeline_field": "customer",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
|
||||
|
||||
class CForm(Document):
|
||||
def validate(self):
|
||||
"""Validate invoice that c-form is applicable
|
||||
and no other c-form is received for that"""
|
||||
|
||||
for d in self.get("invoices"):
|
||||
if d.invoice_no:
|
||||
inv = frappe.db.sql(
|
||||
"""select c_form_applicable, c_form_no from
|
||||
`tabSales Invoice` where name = %s and docstatus = 1""",
|
||||
d.invoice_no,
|
||||
)
|
||||
|
||||
if inv and inv[0][0] != "Yes":
|
||||
frappe.throw(_("C-form is not applicable for Invoice: {0}").format(d.invoice_no))
|
||||
|
||||
elif inv and inv[0][1] and inv[0][1] != self.name:
|
||||
frappe.throw(
|
||||
_(
|
||||
"""Invoice {0} is tagged in another C-form: {1}.
|
||||
If you want to change C-form no for this invoice,
|
||||
please remove invoice no from the previous c-form and then try again""".format(
|
||||
d.invoice_no, inv[0][1]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
elif not inv:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row {0}: Invoice {1} is invalid, it might be cancelled / does not exist. \
|
||||
Please enter a valid Invoice".format(
|
||||
d.idx, d.invoice_no
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
"""Update C-Form No on invoices"""
|
||||
self.set_total_invoiced_amount()
|
||||
|
||||
def on_submit(self):
|
||||
self.set_cform_in_sales_invoices()
|
||||
|
||||
def before_cancel(self):
|
||||
# remove cform reference
|
||||
frappe.db.sql("""update `tabSales Invoice` set c_form_no=null where c_form_no=%s""", self.name)
|
||||
|
||||
def set_cform_in_sales_invoices(self):
|
||||
inv = [d.invoice_no for d in self.get("invoices")]
|
||||
if inv:
|
||||
frappe.db.sql(
|
||||
"""update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)"""
|
||||
% ("%s", "%s", ", ".join(["%s"] * len(inv))),
|
||||
tuple([self.name, self.modified] + inv),
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""update `tabSales Invoice` set c_form_no = null, modified = %s
|
||||
where name not in (%s) and ifnull(c_form_no, '') = %s"""
|
||||
% ("%s", ", ".join(["%s"] * len(inv)), "%s"),
|
||||
tuple([self.modified] + inv + [self.name]),
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("Please enter atleast 1 invoice in the table"))
|
||||
|
||||
def set_total_invoiced_amount(self):
|
||||
total = sum(flt(d.grand_total) for d in self.get("invoices"))
|
||||
frappe.db.set(self, "total_invoiced_amount", total)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoice_details(self, invoice_no):
|
||||
"""Pull details from invoices for referrence"""
|
||||
if invoice_no:
|
||||
inv = frappe.db.get_value(
|
||||
"Sales Invoice",
|
||||
invoice_no,
|
||||
["posting_date", "territory", "base_net_total", "base_grand_total"],
|
||||
as_dict=True,
|
||||
)
|
||||
return {
|
||||
"invoice_date": inv.posting_date,
|
||||
"territory": inv.territory,
|
||||
"net_total": inv.base_net_total,
|
||||
"grand_total": inv.base_grand_total,
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('C-Form')
|
||||
|
||||
|
||||
class TestCForm(unittest.TestCase):
|
||||
pass
|
@ -1 +0,0 @@
|
||||
Invoice detail for parent C-Form.
|
@ -1,168 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:38",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "invoice_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Sales Invoice",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "160px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "160px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "invoice_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"description": "",
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Territory",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Territory",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Net Total",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Grand Total",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "120px"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-07-11 03:27:58.768719",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "C-Form Invoice Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CFormInvoiceDetail(Document):
|
||||
pass
|
@ -29,7 +29,6 @@ def test_create_test_data():
|
||||
"item_name": "_Test Tesla Car",
|
||||
"apply_warehouse_wise_reorder_level": 0,
|
||||
"warehouse": "Stores - _TC",
|
||||
"gst_hsn_code": "999800",
|
||||
"valuation_rate": 5000,
|
||||
"standard_rate": 5000,
|
||||
"item_defaults": [
|
||||
|
@ -1,90 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2018-01-02 15:48:58.768352",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"cgst_account",
|
||||
"sgst_account",
|
||||
"igst_account",
|
||||
"cess_account",
|
||||
"utgst_account",
|
||||
"is_reverse_charge_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "cgst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "CGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "sgst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "SGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "igst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "IGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "cess_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "CESS Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_reverse_charge_account",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Reverse Charge Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "utgst_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "UTGST Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-07 12:59:14.039768",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class GSTAccount(Document):
|
||||
pass
|
@ -1,17 +0,0 @@
|
||||
frappe.ui.form.on("Journal Entry", {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('company_address', function(doc) {
|
||||
if(!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||
filters: {
|
||||
link_doctype: 'Company',
|
||||
link_name: doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
frappe.ui.form.on("Payment Entry", {
|
||||
company: function(frm) {
|
||||
frappe.call({
|
||||
'method': 'frappe.contacts.doctype.address.address.get_default_address',
|
||||
'args': {
|
||||
'doctype': 'Company',
|
||||
'name': frm.doc.company
|
||||
},
|
||||
'callback': function(r) {
|
||||
frm.set_value('company_address', r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
party: function(frm) {
|
||||
if (frm.doc.party_type == "Customer" && frm.doc.party) {
|
||||
frappe.call({
|
||||
'method': 'frappe.contacts.doctype.address.address.get_default_address',
|
||||
'args': {
|
||||
'doctype': 'Customer',
|
||||
'name': frm.doc.party
|
||||
},
|
||||
'callback': function(r) {
|
||||
frm.set_value('customer_address', r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -151,7 +151,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
)
|
||||
return sinv
|
||||
|
||||
def create_payment_entry(self, amount=100, posting_date=nowdate()):
|
||||
def create_payment_entry(self, amount=100, posting_date=nowdate(), customer=None):
|
||||
"""
|
||||
Helper function to populate default values in payment entry
|
||||
"""
|
||||
@ -159,7 +159,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
company=self.company,
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=self.customer,
|
||||
party=customer or self.customer,
|
||||
paid_from=self.debit_to,
|
||||
paid_to=self.bank,
|
||||
paid_amount=amount,
|
||||
@ -529,10 +529,13 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
|
||||
cr_note.cancel()
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
|
||||
pay = get_payment_entry(si.doctype, si.name)
|
||||
pay.references.clear()
|
||||
pay = self.create_payment_entry(
|
||||
amount=amount, posting_date=transaction_date, customer=self.customer3
|
||||
)
|
||||
pay.paid_from = self.debtors_eur
|
||||
pay.paid_from_account_currency = "EUR"
|
||||
pay.source_exchange_rate = exchange_rate
|
||||
pay.received_amount = exchange_rate * amount
|
||||
pay = pay.save().submit()
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
@ -164,8 +164,6 @@
|
||||
"debit_to",
|
||||
"party_account_currency",
|
||||
"is_opening",
|
||||
"c_form_applicable",
|
||||
"c_form_no",
|
||||
"column_break8",
|
||||
"remarks",
|
||||
"sales_team_section_break",
|
||||
@ -1399,23 +1397,6 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "c_form_applicable",
|
||||
"fieldtype": "Select",
|
||||
"label": "C-Form Applicable",
|
||||
"no_copy": 1,
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "c_form_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "C-Form No",
|
||||
"no_copy": 1,
|
||||
"options": "C-Form",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break8",
|
||||
"fieldtype": "Column Break",
|
||||
|
@ -539,7 +539,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.per_received < 100) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frm.events.make_purchase_receipt(frm);
|
||||
}, __('Create'));
|
||||
@ -572,9 +572,10 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
is_subcontracted: function(frm) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
|
||||
},
|
||||
|
||||
|
@ -169,7 +169,8 @@
|
||||
"column_break_114",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference",
|
||||
"per_received"
|
||||
"per_received",
|
||||
"is_old_subcontracting_flow"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -547,7 +548,8 @@
|
||||
"fieldname": "is_subcontracted",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Subcontracted",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "items_section",
|
||||
@ -1365,7 +1367,7 @@
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.update_stock && doc.is_subcontracted",
|
||||
"depends_on": "eval:doc.is_subcontracted",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Warehouse",
|
||||
@ -1416,13 +1418,21 @@
|
||||
"label": "Advance Tax",
|
||||
"options": "Advance Tax",
|
||||
"read_only": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-25 13:31:02.716727",
|
||||
"modified": "2022-06-15 15:40:58.527065",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -502,7 +502,10 @@ class PurchaseInvoice(BuyingController):
|
||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
self.set_consumed_qty_in_po()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
@ -1405,7 +1408,9 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
self.delete_auto_created_batches()
|
||||
self.set_consumed_qty_in_po()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Purchase Invoice');
|
@ -470,37 +470,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
self.assertEqual(tax.tax_amount, expected_values[i][1])
|
||||
self.assertEqual(tax.total, expected_values[i][2])
|
||||
|
||||
def test_purchase_invoice_with_subcontracted_item(self):
|
||||
wrapper = frappe.copy_doc(test_records[0])
|
||||
wrapper.get("items")[0].item_code = "_Test FG Item"
|
||||
wrapper.insert()
|
||||
wrapper.load_from_db()
|
||||
|
||||
expected_values = [["_Test FG Item", 90, 59], ["_Test Item Home Desktop 200", 135, 177]]
|
||||
for i, item in enumerate(wrapper.get("items")):
|
||||
self.assertEqual(item.item_code, expected_values[i][0])
|
||||
self.assertEqual(item.item_tax_amount, expected_values[i][1])
|
||||
self.assertEqual(item.valuation_rate, expected_values[i][2])
|
||||
|
||||
self.assertEqual(wrapper.base_net_total, 1250)
|
||||
|
||||
# tax amounts
|
||||
expected_values = [
|
||||
["_Test Account Shipping Charges - _TC", 100, 1350],
|
||||
["_Test Account Customs Duty - _TC", 125, 1350],
|
||||
["_Test Account Excise Duty - _TC", 140, 1490],
|
||||
["_Test Account Education Cess - _TC", 2.8, 1492.8],
|
||||
["_Test Account S&H Education Cess - _TC", 1.4, 1494.2],
|
||||
["_Test Account CST - _TC", 29.88, 1524.08],
|
||||
["_Test Account VAT - _TC", 156.25, 1680.33],
|
||||
["_Test Account Discount - _TC", 168.03, 1512.30],
|
||||
]
|
||||
|
||||
for i, tax in enumerate(wrapper.get("taxes")):
|
||||
self.assertEqual(tax.account_head, expected_values[i][0])
|
||||
self.assertEqual(tax.tax_amount, expected_values[i][1])
|
||||
self.assertEqual(tax.total, expected_values[i][2])
|
||||
|
||||
def test_purchase_invoice_with_advance(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@ -961,30 +930,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
pi.cancel()
|
||||
self.assertEqual(actual_qty_0, get_qty_after_transaction())
|
||||
|
||||
def test_subcontracting_via_purchase_invoice(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
update_backflush_based_on("BOM")
|
||||
make_stock_entry(
|
||||
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Item Home Desktop 100",
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
qty=100,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted=1
|
||||
)
|
||||
|
||||
self.assertEqual(len(pi.get("supplied_items")), 2)
|
||||
|
||||
rm_supp_cost = sum(d.amount for d in pi.get("supplied_items"))
|
||||
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
|
||||
|
||||
def test_rejected_serial_no(self):
|
||||
pi = make_purchase_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
@ -1474,15 +1419,30 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
def test_purchase_invoice_advance_taxes(self):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
|
||||
company = "_Test Company"
|
||||
|
||||
tds_account_args = {
|
||||
"doctype": "Account",
|
||||
"account_name": "TDS Payable",
|
||||
"account_type": "Tax",
|
||||
"parent_account": frappe.db.get_value(
|
||||
"Account", {"account_name": "Duties and Taxes", "company": company}
|
||||
),
|
||||
"company": company,
|
||||
}
|
||||
|
||||
tds_account = create_account(**tds_account_args)
|
||||
tax_withholding_category = "Test TDS - 194 - Dividends - Individual"
|
||||
|
||||
# Update tax withholding category with current fiscal year and rate details
|
||||
create_tax_witholding_category(tax_withholding_category, company, tds_account)
|
||||
|
||||
# create a new supplier to test
|
||||
supplier = create_supplier(
|
||||
supplier_name="_Test TDS Advance Supplier",
|
||||
tax_withholding_category="TDS - 194 - Dividends - Individual",
|
||||
tax_withholding_category=tax_withholding_category,
|
||||
)
|
||||
|
||||
# Update tax withholding category with current fiscal year and rate details
|
||||
update_tax_witholding_category("_Test Company", "TDS Payable - _TC")
|
||||
|
||||
# Create Purchase Order with TDS applied
|
||||
po = create_purchase_order(
|
||||
do_not_save=1,
|
||||
@ -1498,7 +1458,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
payment_entry = get_payment_entry(dt="Purchase Order", dn=po.name)
|
||||
payment_entry.paid_from = "Cash - _TC"
|
||||
payment_entry.apply_tax_withholding_amount = 1
|
||||
payment_entry.tax_withholding_category = "TDS - 194 - Dividends - Individual"
|
||||
payment_entry.tax_withholding_category = tax_withholding_category
|
||||
payment_entry.save()
|
||||
payment_entry.submit()
|
||||
|
||||
@ -1506,7 +1466,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
expected_gle = [
|
||||
["Cash - _TC", 0, 27000],
|
||||
["Creditors - _TC", 30000, 0],
|
||||
["TDS Payable - _TC", 0, 3000],
|
||||
[tds_account, 0, 3000],
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
@ -1532,7 +1492,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
purchase_invoice.submit()
|
||||
|
||||
# Check GLE for Purchase Invoice
|
||||
# Zero net effect on final TDS Payable on invoice
|
||||
# Zero net effect on final TDS payable on invoice
|
||||
expected_gle = [["_Test Account Cost for Goods Sold - _TC", 30000], ["Creditors - _TC", -30000]]
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
@ -1654,40 +1614,28 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
|
||||
def update_tax_witholding_category(company, account):
|
||||
def create_tax_witholding_category(category_name, company, account):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
|
||||
if not frappe.db.get_value(
|
||||
"Tax Withholding Rate",
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"parent": "TDS - 194 - Dividends - Individual",
|
||||
"from_date": (">=", fiscal_year[1]),
|
||||
"to_date": ("<=", fiscal_year[2]),
|
||||
},
|
||||
):
|
||||
tds_category = frappe.get_doc("Tax Withholding Category", "TDS - 194 - Dividends - Individual")
|
||||
tds_category.set("rates", [])
|
||||
|
||||
tds_category.append(
|
||||
"rates",
|
||||
{
|
||||
"from_date": fiscal_year[1],
|
||||
"to_date": fiscal_year[2],
|
||||
"tax_withholding_rate": 10,
|
||||
"single_threshold": 2500,
|
||||
"cumulative_threshold": 0,
|
||||
},
|
||||
)
|
||||
tds_category.save()
|
||||
|
||||
if not frappe.db.get_value(
|
||||
"Tax Withholding Account", {"parent": "TDS - 194 - Dividends - Individual", "account": account}
|
||||
):
|
||||
tds_category = frappe.get_doc("Tax Withholding Category", "TDS - 194 - Dividends - Individual")
|
||||
tds_category.append("accounts", {"company": company, "account": account})
|
||||
tds_category.save()
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": category_name,
|
||||
"category_name": category_name,
|
||||
"accounts": [{"company": company, "account": account}],
|
||||
"rates": [
|
||||
{
|
||||
"from_date": fiscal_year[1],
|
||||
"to_date": fiscal_year[2],
|
||||
"tax_withholding_rate": 10,
|
||||
"single_threshold": 2500,
|
||||
"cumulative_threshold": 0,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
|
||||
def unlink_payment_on_cancel_of_invoice(enable=1):
|
||||
|
@ -619,10 +619,13 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM"
|
||||
"options": "BOM",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
@ -1,53 +0,0 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||
|
||||
frappe.ui.form.on("Sales Invoice", {
|
||||
setup: function(frm) {
|
||||
frm.set_query('transporter', function() {
|
||||
return {
|
||||
filters: {
|
||||
'is_transporter': 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('driver', function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
'transporter': doc.transporter
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus == 1 && !frm.is_dirty()
|
||||
&& !frm.doc.is_return && !frm.doc.ewaybill) {
|
||||
|
||||
frm.add_custom_button('E-Way Bill JSON', () => {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.utils.generate_ewb_json',
|
||||
args: {
|
||||
'dt': frm.doc.doctype,
|
||||
'dn': [frm.doc.name]
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
const args = {
|
||||
cmd: 'erpnext.regional.india.utils.download_ewb_json',
|
||||
data: r.message,
|
||||
docname: frm.doc.name
|
||||
};
|
||||
open_url_post(frappe.request.url, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}, __("Create"));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
@ -1,174 +0,0 @@
|
||||
var globalOnload = frappe.listview_settings['Sales Invoice'].onload;
|
||||
frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
|
||||
|
||||
// Provision in case onload event is added to sales_invoice.js in future
|
||||
if (globalOnload) {
|
||||
globalOnload(list_view);
|
||||
}
|
||||
|
||||
const action = () => {
|
||||
const selected_docs = list_view.get_checked_items();
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
for (let doc of selected_docs) {
|
||||
if (doc.docstatus !== 1) {
|
||||
frappe.throw(__("E-Way Bill JSON can only be generated from a submitted document"));
|
||||
}
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.utils.generate_ewb_json',
|
||||
args: {
|
||||
'dt': list_view.doctype,
|
||||
'dn': docnames
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
const args = {
|
||||
cmd: 'erpnext.regional.india.utils.download_ewb_json',
|
||||
data: r.message,
|
||||
docname: docnames
|
||||
};
|
||||
open_url_post(frappe.request.url, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
|
||||
|
||||
const generate_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
if (docnames && docnames.length) {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
|
||||
args: { docnames },
|
||||
freeze: true,
|
||||
freeze_message: __('Generating E-Invoices...')
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
message: __('Please select at least one sales invoice to generate IRN'),
|
||||
title: __('No Invoice Selected'),
|
||||
indicator: 'red'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const cancel_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
const fields = [
|
||||
{
|
||||
"label": "Reason",
|
||||
"fieldname": "reason",
|
||||
"fieldtype": "Select",
|
||||
"reqd": 1,
|
||||
"default": "1-Duplicate",
|
||||
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||
},
|
||||
{
|
||||
"label": "Remark",
|
||||
"fieldname": "remark",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
}
|
||||
];
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Cancel IRN"),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
|
||||
args: {
|
||||
doctype: list_view.doctype,
|
||||
docnames,
|
||||
reason: data.reason.split('-')[0],
|
||||
remark: data.remark
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __('Cancelling E-Invoices...'),
|
||||
});
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
|
||||
let einvoicing_enabled = false;
|
||||
frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
|
||||
einvoicing_enabled = enabled;
|
||||
});
|
||||
|
||||
list_view.$result.on("change", "input[type=checkbox]", () => {
|
||||
if (einvoicing_enabled) {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
// show/hide e-invoicing actions when no sales invoices are checked
|
||||
if (docnames && docnames.length) {
|
||||
// prevent adding actions twice if e-invoicing action group already exists
|
||||
if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
|
||||
list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
|
||||
list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
|
||||
}
|
||||
} else {
|
||||
list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
|
||||
list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices generated successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to generate IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices cancelled successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to cancel IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
@ -790,10 +790,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
}
|
||||
|
||||
// India related fields
|
||||
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
|
||||
else hide_field(['c_form_applicable', 'c_form_no']);
|
||||
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
||||
|
@ -175,8 +175,6 @@
|
||||
"debit_to",
|
||||
"party_account_currency",
|
||||
"is_opening",
|
||||
"c_form_applicable",
|
||||
"c_form_no",
|
||||
"column_break8",
|
||||
"unrealized_profit_loss_account",
|
||||
"remarks",
|
||||
@ -1717,28 +1715,6 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "c_form_applicable",
|
||||
"fieldtype": "Select",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "C-Form Applicable",
|
||||
"length": 4,
|
||||
"no_copy": 1,
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "c_form_no",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "C-Form No",
|
||||
"no_copy": 1,
|
||||
"options": "C-Form",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break8",
|
||||
"fieldtype": "Column Break",
|
||||
|
@ -151,7 +151,6 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
self.set_against_income_account()
|
||||
self.validate_c_form()
|
||||
self.validate_time_sheets_are_submitted()
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
||||
if not self.is_return:
|
||||
@ -366,8 +365,6 @@ class SalesInvoice(SellingController):
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.update_serial_no(in_cancel=True)
|
||||
|
||||
self.validate_c_form_on_cancel()
|
||||
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
if self.update_stock == 1:
|
||||
@ -815,25 +812,6 @@ class SalesInvoice(SellingController):
|
||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||
|
||||
def validate_c_form(self):
|
||||
"""Blank C-form no if C-form applicable marked as 'No'"""
|
||||
if self.amended_from and self.c_form_applicable == "No" and self.c_form_no:
|
||||
frappe.db.sql(
|
||||
"""delete from `tabC-Form Invoice Detail` where invoice_no = %s
|
||||
and parent = %s""",
|
||||
(self.amended_from, self.c_form_no),
|
||||
)
|
||||
|
||||
frappe.db.set(self, "c_form_no", "")
|
||||
|
||||
def validate_c_form_on_cancel(self):
|
||||
"""Display message if C-Form no exists on cancellation of Sales Invoice"""
|
||||
if self.c_form_applicable == "Yes" and self.c_form_no:
|
||||
msgprint(
|
||||
_("Please remove this Invoice {0} from C-Form {1}").format(self.name, self.c_form_no),
|
||||
raise_exception=1,
|
||||
)
|
||||
|
||||
def validate_dropship_item(self):
|
||||
for item in self.items:
|
||||
if item.sales_order:
|
||||
@ -1528,9 +1506,7 @@ class SalesInvoice(SellingController):
|
||||
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
|
||||
|
||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||
for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"):
|
||||
self.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
self.set("write_off_amount", reference_doc.get("write_off_amount"))
|
||||
self.due_date = None
|
||||
|
||||
def update_serial_no(self, in_cancel=False):
|
||||
|
@ -24,7 +24,6 @@ from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_d
|
||||
from erpnext.controllers.accounts_controller import update_invoice_status
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
from erpnext.regional.india.utils import get_ewb_data
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
@ -1845,24 +1844,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEqual(d.get(k), expected_values[d.item_code][i])
|
||||
|
||||
def test_item_wise_tax_breakup_india(self):
|
||||
frappe.flags.country = "India"
|
||||
|
||||
si = self.create_si_to_test_tax_breakup()
|
||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
|
||||
|
||||
expected_itemised_tax = {
|
||||
"_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}},
|
||||
"_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}},
|
||||
}
|
||||
expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0}
|
||||
|
||||
self.assertEqual(itemised_tax, expected_itemised_tax)
|
||||
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
|
||||
|
||||
frappe.flags.country = None
|
||||
|
||||
def test_item_wise_tax_breakup_outside_india(self):
|
||||
def test_item_wise_tax_breakup(self):
|
||||
frappe.flags.country = "United States"
|
||||
|
||||
si = self.create_si_to_test_tax_breakup()
|
||||
@ -1886,7 +1868,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 100,
|
||||
"rate": 50,
|
||||
@ -1899,7 +1880,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item 2",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 100,
|
||||
"rate": 50,
|
||||
@ -1986,7 +1966,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 1,
|
||||
"rate": rate,
|
||||
@ -2659,135 +2638,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||
|
||||
def test_eway_bill_json(self):
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
|
||||
si.submit()
|
||||
|
||||
data = get_ewb_data("Sales Invoice", [si.name])
|
||||
|
||||
self.assertEqual(data["version"], "1.0.0421")
|
||||
self.assertEqual(data["billLists"][0]["fromGstin"], "27AAECE4835E1ZR")
|
||||
self.assertEqual(data["billLists"][0]["fromTrdName"], "_Test Company")
|
||||
self.assertEqual(data["billLists"][0]["toTrdName"], "_Test Customer")
|
||||
self.assertEqual(data["billLists"][0]["vehicleType"], "R")
|
||||
self.assertEqual(data["billLists"][0]["totalValue"], 60000)
|
||||
self.assertEqual(data["billLists"][0]["cgstValue"], 5400)
|
||||
self.assertEqual(data["billLists"][0]["sgstValue"], 5400)
|
||||
self.assertEqual(data["billLists"][0]["vehicleNo"], "KA12KA1234")
|
||||
self.assertEqual(data["billLists"][0]["itemList"][0]["taxableAmount"], 60000)
|
||||
self.assertEqual(data["billLists"][0]["actualFromStateCode"], 7)
|
||||
self.assertEqual(data["billLists"][0]["fromStateCode"], 27)
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
einvoice_settings = frappe.get_doc("E Invoice Settings")
|
||||
einvoice_settings.enable = 1
|
||||
einvoice_settings.applicable_from = nowdate()
|
||||
einvoice_settings.append(
|
||||
"credentials",
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"gstin": "27AAECE4835E1ZR",
|
||||
"username": "test",
|
||||
"password": "test",
|
||||
},
|
||||
)
|
||||
einvoice_settings.save()
|
||||
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = "India"
|
||||
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
self.assertRaises(frappe.ValidationError, si.submit)
|
||||
|
||||
si.irn = "test_irn"
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
einvoice_settings = frappe.get_doc("E Invoice Settings")
|
||||
einvoice_settings.enable = 0
|
||||
einvoice_settings.save()
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.discount_amount = 100
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
self.assertTrue(einvoice["EwbDtls"])
|
||||
validate_totals(einvoice)
|
||||
|
||||
si.apply_discount_on = "Net Total"
|
||||
si.save()
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
[d.set("included_in_print_rate", 1) for d in si.taxes]
|
||||
si.save()
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
def test_einvoice_discounts(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||
|
||||
# Normal Itemized Discount
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.apply_discount_on = ""
|
||||
si.items[0].discount_amount = 4000
|
||||
si.items[1].discount_amount = 300
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000)
|
||||
self.assertEqual(einvoice["ItemList"][1]["Discount"], 300)
|
||||
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
|
||||
|
||||
# Invoice Discount on net total
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.apply_discount_on = "Net Total"
|
||||
si.discount_amount = 400
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83)
|
||||
self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17)
|
||||
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
|
||||
|
||||
# Invoice Discount on grand total (Itemized Discount)
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.discount_amount = 400
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5)
|
||||
self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48)
|
||||
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
|
||||
|
||||
# Invoice Discount on grand total (Cash/Non-Trade Discount)
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.is_cash_or_non_trade_discount = 1
|
||||
si.discount_amount = 400
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
|
||||
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
|
||||
self.assertEqual(einvoice["ValDtls"]["Discount"], 400)
|
||||
|
||||
def test_item_tax_net_range(self):
|
||||
item = create_item("T Shirt")
|
||||
|
||||
@ -3258,7 +3108,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_sales_invoice_with_disabled_account(self):
|
||||
try:
|
||||
account = frappe.get_doc("Account", "VAT 5% - _TC")
|
||||
account_name = "Sales Expenses - _TC"
|
||||
account = frappe.get_doc("Account", account_name)
|
||||
account.disabled = 1
|
||||
account.save()
|
||||
|
||||
@ -3270,10 +3121,10 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT 5% - _TC",
|
||||
"account_head": account_name,
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "VAT @ 5.0",
|
||||
"rate": 9,
|
||||
"description": "Commission",
|
||||
"rate": 5,
|
||||
},
|
||||
)
|
||||
si.save()
|
||||
@ -3381,177 +3232,6 @@ def get_sales_invoice_for_e_invoice():
|
||||
return si
|
||||
|
||||
|
||||
def make_test_address_for_ewaybill():
|
||||
if not frappe.db.exists("Address", "_Test Address for Eway bill-Billing"):
|
||||
address = frappe.get_doc(
|
||||
{
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_line2": "_Test Address Line 2",
|
||||
"address_title": "_Test Address for Eway bill",
|
||||
"address_type": "Billing",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+910000000000",
|
||||
"gstin": "27AAECE4835E1ZR",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "401108",
|
||||
}
|
||||
).insert()
|
||||
|
||||
address.append("links", {"link_doctype": "Company", "link_name": "_Test Company"})
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists("Address", "_Test Customer-Address for Eway bill-Billing"):
|
||||
address = frappe.get_doc(
|
||||
{
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_line2": "_Test Address Line 2",
|
||||
"address_title": "_Test Customer-Address for Eway bill",
|
||||
"address_type": "Billing",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+910000000000",
|
||||
"gstin": "27AACCM7806M1Z3",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "410038",
|
||||
}
|
||||
).insert()
|
||||
|
||||
address.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists("Address", "_Test Customer-Address for Eway bill-Shipping"):
|
||||
address = frappe.get_doc(
|
||||
{
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_line2": "_Test Address Line 2",
|
||||
"address_title": "_Test Customer-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+910000000000",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "410098",
|
||||
}
|
||||
).insert()
|
||||
|
||||
address.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists("Address", "_Test Dispatch-Address for Eway bill-Shipping"):
|
||||
address = frappe.get_doc(
|
||||
{
|
||||
"address_line1": "_Test Dispatch Address Line 1",
|
||||
"address_line2": "_Test Dispatch Address Line 2",
|
||||
"address_title": "_Test Dispatch-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 0,
|
||||
"phone": "+910000000000",
|
||||
"gstin": "07AAACC1206D1ZI",
|
||||
"gst_state": "Delhi",
|
||||
"gst_state_number": "07",
|
||||
"pincode": "1100101",
|
||||
}
|
||||
).insert()
|
||||
|
||||
address.save()
|
||||
|
||||
|
||||
def make_test_transporter_for_ewaybill():
|
||||
if not frappe.db.exists("Supplier", "_Test Transporter"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Transporter",
|
||||
"country": "India",
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"supplier_type": "Company",
|
||||
"is_transporter": 1,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_sales_invoice_for_ewaybill():
|
||||
make_test_address_for_ewaybill()
|
||||
make_test_transporter_for_ewaybill()
|
||||
|
||||
gst_settings = frappe.get_doc("GST Settings")
|
||||
|
||||
gst_account = frappe.get_all(
|
||||
"GST Account",
|
||||
fields=["cgst_account", "sgst_account", "igst_account"],
|
||||
filters={"company": "_Test Company"},
|
||||
)
|
||||
|
||||
if not gst_account:
|
||||
gst_settings.append(
|
||||
"gst_accounts",
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"cgst_account": "Output Tax CGST - _TC",
|
||||
"sgst_account": "Output Tax SGST - _TC",
|
||||
"igst_account": "Output Tax IGST - _TC",
|
||||
},
|
||||
)
|
||||
|
||||
gst_settings.save()
|
||||
|
||||
si = create_sales_invoice(do_not_save=1, rate="60000")
|
||||
|
||||
si.distance = 2000
|
||||
si.company_address = "_Test Address for Eway bill-Billing"
|
||||
si.customer_address = "_Test Customer-Address for Eway bill-Billing"
|
||||
si.shipping_address_name = "_Test Customer-Address for Eway bill-Shipping"
|
||||
si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
|
||||
si.vehicle_no = "KA12KA1234"
|
||||
si.gst_category = "Registered Regular"
|
||||
si.mode_of_transport = "Road"
|
||||
si.transporter = "_Test Transporter"
|
||||
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "Output Tax CGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "CGST @ 9.0",
|
||||
"rate": 9,
|
||||
},
|
||||
)
|
||||
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "Output Tax SGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "SGST @ 9.0",
|
||||
"rate": 9,
|
||||
},
|
||||
)
|
||||
|
||||
return si
|
||||
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit, posting_date
|
||||
@ -3594,7 +3274,6 @@ def create_sales_invoice(**args):
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"item_name": args.item_name or "_Test Item",
|
||||
"description": args.description or "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 1,
|
||||
"uom": args.uom or "Nos",
|
||||
@ -3647,7 +3326,6 @@ def create_sales_invoice_against_cost_center(**args):
|
||||
"items",
|
||||
{
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 1,
|
||||
"rate": args.rate or 100,
|
||||
|
@ -1,180 +0,0 @@
|
||||
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
||||
|
||||
<div class="page-break">
|
||||
{% if doc.signed_einvoice %}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
{% endif %}
|
||||
<div class="print-heading">
|
||||
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
|
||||
</div>
|
||||
</div>
|
||||
{% if print_settings.repeat_header_footer %}
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if not no_letterhead and footer %}
|
||||
<div class="letter-head-footer">
|
||||
{{ footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-center small page-number visible-pdf">
|
||||
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
|
||||
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||
<div class="col-xs-8 column-break">
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>IRN</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Ack. No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Ack. Date</label></div>
|
||||
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Category</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document Type</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 column-break">
|
||||
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
|
||||
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||
{%- set seller = einvoice.SellerDtls -%}
|
||||
<div class="col-xs-6 column-break">
|
||||
<h5 style="margin-bottom: 5px;">Seller</h5>
|
||||
<p>{{ seller.Gstin }}</p>
|
||||
<p>{{ seller.LglNm }}</p>
|
||||
<p>{{ seller.Addr1 }}</p>
|
||||
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
|
||||
<p>{{ seller.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
|
||||
|
||||
{%- if einvoice.ShipDtls -%}
|
||||
{%- set shipping = einvoice.ShipDtls -%}
|
||||
<h5 style="margin-bottom: 5px;">Shipped From</h5>
|
||||
<p>{{ shipping.Gstin }}</p>
|
||||
<p>{{ shipping.LglNm }}</p>
|
||||
<p>{{ shipping.Addr1 }}</p>
|
||||
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
|
||||
<p>{{ shipping.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- set buyer = einvoice.BuyerDtls -%}
|
||||
<div class="col-xs-6 column-break">
|
||||
<h5 style="margin-bottom: 5px;">Buyer</h5>
|
||||
<p>{{ buyer.Gstin }}</p>
|
||||
<p>{{ buyer.LglNm }}</p>
|
||||
<p>{{ buyer.Addr1 }}</p>
|
||||
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
|
||||
<p>{{ buyer.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
|
||||
|
||||
{%- if einvoice.DispDtls -%}
|
||||
{%- set dispatch = einvoice.DispDtls -%}
|
||||
<h5 style="margin-bottom: 5px;">Dispatched From</h5>
|
||||
{%- if dispatch.Gstin -%} <p>{{ dispatch.Gstin }}</p> {% endif %}
|
||||
<p>{{ dispatch.LglNm }}</p>
|
||||
<p>{{ dispatch.Addr1 }}</p>
|
||||
{%- if dispatch.Addr2 -%} <p>{{ dispatch.Addr2 }}</p> {% endif %}
|
||||
<p>{{ dispatch.Loc }}</p>
|
||||
<p>{{ frappe.db.get_value("Address", doc.dispatch_address_name, "gst_state") }} - {{ dispatch.Pin }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left" style="width: 3%;">Sr. No.</th>
|
||||
<th class="text-left">Item</th>
|
||||
<th class="text-left" style="width: 10%;">HSN Code</th>
|
||||
<th class="text-left" style="width: 5%;">Qty</th>
|
||||
<th class="text-left" style="width: 5%;">UOM</th>
|
||||
<th class="text-left">Rate</th>
|
||||
<th class="text-left" style="width: 5%;">Discount</th>
|
||||
<th class="text-left">Taxable Amount</th>
|
||||
<th class="text-left" style="width: 7%;">Tax Rate</th>
|
||||
<th class="text-left" style="width: 5%;">Other Charges</th>
|
||||
<th class="text-left">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in einvoice.ItemList %}
|
||||
<tr>
|
||||
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
|
||||
<td class="text-left">{{ item.PrdDesc }}</td>
|
||||
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
|
||||
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
|
||||
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
|
||||
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
|
||||
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
|
||||
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Taxable Amount</th>
|
||||
<th class="text-left">CGST</th>
|
||||
<th class="text-left"">SGST</th>
|
||||
<th class="text-left">IGST</th>
|
||||
<th class="text-left">CESS</th>
|
||||
<th class="text-left" style="width: 10%;">State CESS</th>
|
||||
<th class="text-left">Discount</th>
|
||||
<th class="text-left" style="width: 10%;">Other Charges</th>
|
||||
<th class="text-left" style="width: 10%;">Round Off</th>
|
||||
<th class="text-left">Total Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- set value_details = einvoice.ValDtls -%}
|
||||
<tr>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
||||
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center" style="color: var(--gray-500); font-size: 14px;">
|
||||
You must generate IRN before you can preview GST E-Invoice.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"align_labels_right": 1,
|
||||
"creation": "2020-10-10 18:01:21.032914",
|
||||
"custom_format": 0,
|
||||
"default_print_language": "en-US",
|
||||
"disabled": 1,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "Default",
|
||||
"html": "",
|
||||
"idx": 0,
|
||||
"line_breaks": 1,
|
||||
"modified": "2020-10-23 19:54:40.634936",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST E-Invoice",
|
||||
"owner": "Administrator",
|
||||
"print_format_builder": 0,
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 1,
|
||||
"standard": "Yes"
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -5,7 +5,7 @@
|
||||
"label": "Profit and Loss"
|
||||
}
|
||||
],
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goods and Services Tax (GST India)\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]",
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]",
|
||||
"creation": "2020-03-02 15:41:59.515192",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
@ -777,147 +777,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Goods and Services Tax (GST India)",
|
||||
"link_count": 0,
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "GST Settings",
|
||||
"link_count": 0,
|
||||
"link_to": "GST Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "GST HSN Code",
|
||||
"link_count": 0,
|
||||
"link_to": "GST HSN Code",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "GSTR-1",
|
||||
"link_count": 0,
|
||||
"link_to": "GSTR-1",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "GSTR-2",
|
||||
"link_count": 0,
|
||||
"link_to": "GSTR-2",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "GSTR 3B Report",
|
||||
"link_count": 0,
|
||||
"link_to": "GSTR 3B Report",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "GST Sales Register",
|
||||
"link_count": 0,
|
||||
"link_to": "GST Sales Register",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "GST Purchase Register",
|
||||
"link_count": 0,
|
||||
"link_to": "GST Purchase Register",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "GST Itemised Sales Register",
|
||||
"link_count": 0,
|
||||
"link_to": "GST Itemised Sales Register",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "GST Itemised Purchase Register",
|
||||
"link_count": 0,
|
||||
"link_to": "GST Itemised Purchase Register",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "C-Form",
|
||||
"link_count": 0,
|
||||
"link_to": "C-Form",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Lower Deduction Certificate",
|
||||
"link_count": 0,
|
||||
"link_to": "Lower Deduction Certificate",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -1159,6 +1018,17 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Lower Deduction Certificate",
|
||||
"link_count": 0,
|
||||
"link_to": "Lower Deduction Certificate",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "India",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -1223,7 +1093,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2022-06-10 15:49:42.990860",
|
||||
"modified": "2022-06-24 05:41:09.236458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
|
@ -740,51 +740,6 @@ class TestDepreciationMethods(AssetSetup):
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_discounted_wdv_depreciation_rate_for_indian_region(self):
|
||||
# set indian company
|
||||
company_flag = frappe.flags.company
|
||||
frappe.flags.company = "_Test Company"
|
||||
|
||||
finance_book = frappe.new_doc("Finance Book")
|
||||
finance_book.finance_book_name = "Income Tax"
|
||||
finance_book.for_income_tax = 1
|
||||
finance_book.insert(ignore_if_duplicate=True)
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2030-07-12",
|
||||
purchase_date="2030-01-01",
|
||||
finance_book=finance_book.name,
|
||||
depreciation_method="Written Down Value",
|
||||
expected_value_after_useful_life=12500,
|
||||
depreciation_start_date="2030-12-31",
|
||||
total_number_of_depreciations=3,
|
||||
frequency_of_depreciation=12,
|
||||
)
|
||||
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 11849.32, 11849.32],
|
||||
["2031-12-31", 44075.34, 55924.66],
|
||||
["2032-12-31", 22037.67, 77962.33],
|
||||
["2033-07-12", 9537.67, 87500.0],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
[
|
||||
cstr(d.schedule_date),
|
||||
flt(d.depreciation_amount, 2),
|
||||
flt(d.accumulated_depreciation_amount, 2),
|
||||
]
|
||||
for d in asset.get("schedules")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
# reset indian company
|
||||
frappe.flags.company = company_flag
|
||||
|
||||
|
||||
class TestDepreciationBasics(AssetSetup):
|
||||
def test_depreciation_without_pro_rata(self):
|
||||
|
@ -12,9 +12,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
)
|
||||
from erpnext.assets.doctype.asset.asset import get_depreciation_amount
|
||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||
from erpnext.regional.india.utils import (
|
||||
get_depreciation_amount as get_depreciation_amount_for_india,
|
||||
)
|
||||
|
||||
|
||||
class AssetValueAdjustment(Document):
|
||||
@ -133,10 +130,7 @@ class AssetValueAdjustment(Document):
|
||||
depreciation_amount = days * rate_per_day
|
||||
from_date = data.schedule_date
|
||||
else:
|
||||
if country == "India":
|
||||
depreciation_amount = get_depreciation_amount_for_india(asset, value_after_depreciation, d)
|
||||
else:
|
||||
depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
|
||||
depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
|
||||
|
||||
if depreciation_amount:
|
||||
value_after_depreciation -= flt(depreciation_amount)
|
||||
|
@ -8,15 +8,17 @@ frappe.provide("erpnext.accounts.dimensions");
|
||||
frappe.ui.form.on("Purchase Order", {
|
||||
setup: function(frm) {
|
||||
|
||||
frm.set_query("reserve_warehouse", "supplied_items", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"name": ['!=', frm.doc.supplier_warehouse],
|
||||
"is_group": 0
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
frm.set_query("reserve_warehouse", "supplied_items", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"name": ['!=', frm.doc.supplier_warehouse],
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_indicator_formatter('item_code',
|
||||
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
|
||||
@ -28,12 +30,67 @@ frappe.ui.form.on("Purchase Order", {
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("fg_item", "items", function() {
|
||||
return {
|
||||
filters: {
|
||||
'is_sub_contracted_item': 1,
|
||||
'default_bom': ['!=', '']
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.is_old_subcontracting_flow) {
|
||||
frm.trigger('get_materials_from_supplier');
|
||||
|
||||
$('a.grey-link').each(function () {
|
||||
var id = $(this).children(':first-child').attr('data-label');
|
||||
if (id == 'Duplicate') {
|
||||
$(this).remove();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get_materials_from_supplier: function(frm) {
|
||||
let po_details = [];
|
||||
|
||||
if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
|
||||
frm.doc.supplied_items.forEach(d => {
|
||||
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||
po_details.push(d.name)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (po_details && po_details.length) {
|
||||
frm.add_custom_button(__('Return of Components'), () => {
|
||||
frm.call({
|
||||
method: 'erpnext.controllers.subcontracting_controller.get_materials_from_supplier',
|
||||
freeze: true,
|
||||
freeze_message: __('Creating Stock Entry'),
|
||||
args: {
|
||||
subcontract_order: frm.doc.name,
|
||||
rm_details: po_details,
|
||||
order_doctype: cur_frm.doc.doctype
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r && r.message) {
|
||||
const doc = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doc[0].doctype, doc[0].name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, __('Create'));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
set_schedule_date(frm);
|
||||
if (!frm.doc.transaction_date){
|
||||
@ -52,39 +109,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
frm.set_value("tax_withholding_category", frm.supplier_tds);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger('get_materials_from_supplier');
|
||||
},
|
||||
|
||||
get_materials_from_supplier: function(frm) {
|
||||
let po_details = [];
|
||||
|
||||
if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
|
||||
frm.doc.supplied_items.forEach(d => {
|
||||
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||
po_details.push(d.name)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (po_details && po_details.length) {
|
||||
frm.add_custom_button(__('Return of Components'), () => {
|
||||
frm.call({
|
||||
method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier',
|
||||
freeze: true,
|
||||
freeze_message: __('Creating Stock Entry'),
|
||||
args: { purchase_order: frm.doc.name, po_details: po_details },
|
||||
callback: function(r) {
|
||||
if (r && r.message) {
|
||||
const doc = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doc[0].doctype, doc[0].name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Purchase Order Item", {
|
||||
@ -97,6 +121,16 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
set_schedule_date(frm);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
qty: function(frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.qty) {
|
||||
row.fg_item_qty = row.qty;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -105,12 +139,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
this.frm.custom_make_buttons = {
|
||||
'Purchase Receipt': 'Purchase Receipt',
|
||||
'Purchase Invoice': 'Purchase Invoice',
|
||||
'Stock Entry': 'Material to Supplier',
|
||||
'Payment Entry': 'Payment',
|
||||
'Subcontracting Order': 'Subcontracting Order',
|
||||
'Stock Entry': 'Material to Supplier'
|
||||
}
|
||||
|
||||
super.setup();
|
||||
|
||||
}
|
||||
|
||||
refresh(doc, cdt, cdn) {
|
||||
@ -142,14 +176,17 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
|
||||
if(!in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
|
||||
this.frm.add_custom_button(__('Update Items'), () => {
|
||||
erpnext.utils.update_child_items({
|
||||
frm: this.frm,
|
||||
child_docname: "items",
|
||||
child_doctype: "Purchase Order Detail",
|
||||
cannot_add_row: false,
|
||||
})
|
||||
});
|
||||
// Don't add Update Items button if the PO is following the new subcontracting flow.
|
||||
if (!(this.frm.doc.is_subcontracted && !this.frm.doc.is_old_subcontracting_flow)) {
|
||||
this.frm.add_custom_button(__('Update Items'), () => {
|
||||
erpnext.utils.update_child_items({
|
||||
frm: this.frm,
|
||||
child_docname: "items",
|
||||
child_doctype: "Purchase Order Detail",
|
||||
cannot_add_row: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.frm.has_perm("submit")) {
|
||||
if(flt(doc.per_billed, 6) < 100 || flt(doc.per_received, 6) < 100) {
|
||||
@ -177,9 +214,15 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
if (doc.status != "On Hold") {
|
||||
if(flt(doc.per_received) < 100 && allow_receipt) {
|
||||
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
|
||||
if(doc.is_subcontracted && me.has_unsupplied_items()) {
|
||||
cur_frm.add_custom_button(__('Material to Supplier'),
|
||||
function() { me.make_stock_entry(); }, __("Transfer"));
|
||||
if (doc.is_subcontracted) {
|
||||
if (doc.is_old_subcontracting_flow) {
|
||||
if (me.has_unsupplied_items()) {
|
||||
cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(flt(doc.per_billed) < 100)
|
||||
@ -370,10 +413,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
|
||||
_make_rm_stock_entry(rm_items) {
|
||||
frappe.call({
|
||||
method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry",
|
||||
method:"erpnext.controllers.subcontracting_controller.make_rm_stock_entry",
|
||||
args: {
|
||||
purchase_order: cur_frm.doc.name,
|
||||
rm_items: rm_items
|
||||
subcontract_order: cur_frm.doc.name,
|
||||
rm_items: rm_items,
|
||||
order_doctype: cur_frm.doc.doctype
|
||||
}
|
||||
,
|
||||
callback: function(r) {
|
||||
@ -405,6 +449,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
})
|
||||
}
|
||||
|
||||
make_subcontracting_order() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_subcontracting_order",
|
||||
frm: cur_frm,
|
||||
freeze_message: __("Creating Subcontracting Order ...")
|
||||
})
|
||||
}
|
||||
|
||||
add_from_mappers() {
|
||||
var me = this;
|
||||
this.frm.add_custom_button(__('Material Request'),
|
||||
@ -613,15 +665,17 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc,
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn]
|
||||
return {
|
||||
filters: [
|
||||
['BOM', 'item', '=', d.item_code],
|
||||
['BOM', 'is_active', '=', '1'],
|
||||
['BOM', 'docstatus', '=', '1'],
|
||||
['BOM', 'company', '=', doc.company]
|
||||
]
|
||||
if (cur_frm.doc.is_old_subcontracting_flow) {
|
||||
cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn]
|
||||
return {
|
||||
filters: [
|
||||
['BOM', 'item', '=', d.item_code],
|
||||
['BOM', 'is_active', '=', '1'],
|
||||
['BOM', 'docstatus', '=', '1'],
|
||||
['BOM', 'company', '=', doc.company]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,7 +688,7 @@ function set_schedule_date(frm) {
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
});
|
||||
});
|
@ -16,6 +16,8 @@
|
||||
"supplier_name",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"is_subcontracted",
|
||||
"supplier_warehouse",
|
||||
"column_break1",
|
||||
"company",
|
||||
"transaction_date",
|
||||
@ -55,10 +57,7 @@
|
||||
"price_list_currency",
|
||||
"plc_conversion_rate",
|
||||
"ignore_pricing_rule",
|
||||
"sec_warehouse",
|
||||
"is_subcontracted",
|
||||
"col_break_warehouse",
|
||||
"supplier_warehouse",
|
||||
"section_break_45",
|
||||
"before_items_section",
|
||||
"scan_barcode",
|
||||
"items_col_break",
|
||||
@ -142,7 +141,8 @@
|
||||
"party_account_currency",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"inter_company_order_reference"
|
||||
"inter_company_order_reference",
|
||||
"is_old_subcontracting_flow"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -158,7 +158,8 @@
|
||||
"hidden": 1,
|
||||
"label": "Title",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
@ -443,11 +444,6 @@
|
||||
"permlevel": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sec_warehouse",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subcontracting"
|
||||
},
|
||||
{
|
||||
"description": "Sets 'Warehouse' in each row of the Items table.",
|
||||
"fieldname": "set_warehouse",
|
||||
@ -456,15 +452,10 @@
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break_warehouse",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_subcontracted",
|
||||
"fieldtype": "Check",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Is Subcontracted",
|
||||
"print_hide": 1
|
||||
},
|
||||
@ -1142,6 +1133,10 @@
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_45",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
@ -1163,13 +1158,21 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-26 12:16:38.694276",
|
||||
"modified": "2022-06-15 15:40:58.527065",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -69,8 +69,12 @@ class PurchaseOrder(BuyingController):
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_for_subcontracting()
|
||||
self.validate_minimum_order_qty()
|
||||
self.validate_bom_for_subcontracting_items()
|
||||
self.create_raw_materials_supplied("supplied_items")
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.validate_bom_for_subcontracting_items()
|
||||
self.create_raw_materials_supplied()
|
||||
|
||||
self.validate_fg_item_for_subcontracting()
|
||||
self.set_received_qty_for_drop_ship_items()
|
||||
validate_inter_company_party(
|
||||
self.doctype, self.supplier, self.company, self.inter_company_order_reference
|
||||
@ -194,12 +198,38 @@ class PurchaseOrder(BuyingController):
|
||||
)
|
||||
|
||||
def validate_bom_for_subcontracting_items(self):
|
||||
if self.is_subcontracted:
|
||||
for item in self.items:
|
||||
if not item.bom:
|
||||
frappe.throw(
|
||||
_("Row #{0}: BOM is not specified for subcontracting item {0}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
|
||||
def validate_fg_item_for_subcontracting(self):
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
for item in self.items:
|
||||
if not item.bom:
|
||||
if not item.fg_item:
|
||||
frappe.throw(
|
||||
_("BOM is not specified for subcontracting item {0} at row {1}").format(
|
||||
item.item_code, item.idx
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}"
|
||||
).format(item.idx, item.fg_item, item.item_code)
|
||||
)
|
||||
elif not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item)
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
|
||||
@ -294,9 +324,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.set_status(update=True, status=status)
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
if self.is_subcontracted:
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
|
||||
@ -310,9 +338,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
self.validate_budget()
|
||||
|
||||
if self.is_subcontracted:
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
frappe.get_doc("Authorization Control").validate_approving_authority(
|
||||
self.doctype, self.company, self.base_grand_total
|
||||
@ -332,9 +358,7 @@ class PurchaseOrder(BuyingController):
|
||||
if self.has_drop_ship_item():
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
|
||||
if self.is_subcontracted:
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
frappe.db.set(self, "status", "Cancelled")
|
||||
@ -405,10 +429,11 @@ class PurchaseOrder(BuyingController):
|
||||
item.received_qty = item.qty
|
||||
|
||||
def update_reserved_qty_for_subcontract(self):
|
||||
for d in self.supplied_items:
|
||||
if d.rm_item_code:
|
||||
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
|
||||
stock_bin.update_reserved_qty_for_sub_contracting()
|
||||
if self.is_old_subcontracting_flow:
|
||||
for d in self.supplied_items:
|
||||
if d.rm_item_code:
|
||||
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
|
||||
stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
|
||||
|
||||
def update_receiving_percentage(self):
|
||||
total_qty, received_qty = 0.0, 0.0
|
||||
@ -587,80 +612,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
return doc
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_rm_stock_entry(purchase_order, rm_items):
|
||||
rm_items_list = rm_items
|
||||
|
||||
if isinstance(rm_items, str):
|
||||
rm_items_list = json.loads(rm_items)
|
||||
elif not rm_items:
|
||||
frappe.throw(_("No Items available for transfer"))
|
||||
|
||||
if rm_items_list:
|
||||
fg_items = list(set(d["item_code"] for d in rm_items_list))
|
||||
else:
|
||||
frappe.throw(_("No Items selected for transfer"))
|
||||
|
||||
if purchase_order:
|
||||
purchase_order = frappe.get_doc("Purchase Order", purchase_order)
|
||||
|
||||
if fg_items:
|
||||
items = tuple(set(d["rm_item_code"] for d in rm_items_list))
|
||||
item_wh = get_item_details(items)
|
||||
|
||||
stock_entry = frappe.new_doc("Stock Entry")
|
||||
stock_entry.purpose = "Send to Subcontractor"
|
||||
stock_entry.purchase_order = purchase_order.name
|
||||
stock_entry.supplier = purchase_order.supplier
|
||||
stock_entry.supplier_name = purchase_order.supplier_name
|
||||
stock_entry.supplier_address = purchase_order.supplier_address
|
||||
stock_entry.address_display = purchase_order.address_display
|
||||
stock_entry.company = purchase_order.company
|
||||
stock_entry.to_warehouse = purchase_order.supplier_warehouse
|
||||
stock_entry.set_stock_entry_type()
|
||||
|
||||
for item_code in fg_items:
|
||||
for rm_item_data in rm_items_list:
|
||||
if rm_item_data["item_code"] == item_code:
|
||||
rm_item_code = rm_item_data["rm_item_code"]
|
||||
items_dict = {
|
||||
rm_item_code: {
|
||||
"po_detail": rm_item_data.get("name"),
|
||||
"item_name": rm_item_data["item_name"],
|
||||
"description": item_wh.get(rm_item_code, {}).get("description", ""),
|
||||
"qty": rm_item_data["qty"],
|
||||
"from_warehouse": rm_item_data["warehouse"],
|
||||
"stock_uom": rm_item_data["stock_uom"],
|
||||
"serial_no": rm_item_data.get("serial_no"),
|
||||
"batch_no": rm_item_data.get("batch_no"),
|
||||
"main_item_code": rm_item_data["item_code"],
|
||||
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
|
||||
}
|
||||
}
|
||||
stock_entry.add_to_stock_entry_detail(items_dict)
|
||||
|
||||
stock_entry.set_missing_values()
|
||||
return stock_entry.as_dict()
|
||||
else:
|
||||
frappe.throw(_("No Items selected for transfer"))
|
||||
return purchase_order.name
|
||||
|
||||
|
||||
def get_item_details(items):
|
||||
item_details = {}
|
||||
for d in frappe.db.sql(
|
||||
"""select item_code, description, allow_alternative_item from `tabItem`
|
||||
where name in ({0})""".format(
|
||||
", ".join(["%s"] * len(items))
|
||||
),
|
||||
items,
|
||||
as_dict=1,
|
||||
):
|
||||
item_details[d.item_code] = d
|
||||
|
||||
return item_details
|
||||
|
||||
|
||||
def get_list_context(context=None):
|
||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||
|
||||
@ -691,61 +642,61 @@ def make_inter_company_sales_order(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_materials_from_supplier(purchase_order, po_details):
|
||||
if isinstance(po_details, str):
|
||||
po_details = json.loads(po_details)
|
||||
|
||||
doc = frappe.get_cached_doc("Purchase Order", purchase_order)
|
||||
doc.initialized_fields()
|
||||
doc.purchase_orders = [doc.name]
|
||||
doc.get_available_materials()
|
||||
|
||||
if not doc.available_materials:
|
||||
frappe.throw(
|
||||
_("Materials are already received against the purchase order {0}").format(purchase_order)
|
||||
)
|
||||
|
||||
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
|
||||
def make_subcontracting_order(source_name, target_doc=None):
|
||||
return get_mapped_subcontracting_order(source_name, target_doc)
|
||||
|
||||
|
||||
def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
|
||||
ste_doc = frappe.new_doc("Stock Entry")
|
||||
ste_doc.purpose = "Material Transfer"
|
||||
ste_doc.purchase_order = po_doc.name
|
||||
ste_doc.company = po_doc.company
|
||||
ste_doc.is_return = 1
|
||||
def get_mapped_subcontracting_order(source_name, target_doc=None):
|
||||
|
||||
for key, value in available_materials.items():
|
||||
if not value.qty:
|
||||
continue
|
||||
if target_doc and isinstance(target_doc, str):
|
||||
target_doc = json.loads(target_doc)
|
||||
for key in ["service_items", "items", "supplied_items"]:
|
||||
if key in target_doc:
|
||||
del target_doc[key]
|
||||
target_doc = json.dumps(target_doc)
|
||||
|
||||
if value.batch_no:
|
||||
for batch_no, qty in value.batch_no.items():
|
||||
if qty > 0:
|
||||
add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no)
|
||||
else:
|
||||
add_items_in_ste(ste_doc, value, value.qty, po_details)
|
||||
|
||||
ste_doc.set_stock_entry_type()
|
||||
ste_doc.set_missing_values()
|
||||
|
||||
return ste_doc
|
||||
|
||||
|
||||
def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
|
||||
item = ste_doc.append("items", row.item_details)
|
||||
|
||||
po_detail = list(set(row.po_details).intersection(po_details))
|
||||
item.update(
|
||||
target_doc = get_mapped_doc(
|
||||
"Purchase Order",
|
||||
source_name,
|
||||
{
|
||||
"qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"basic_rate": row.item_details["rate"],
|
||||
"po_detail": po_detail[0] if po_detail else "",
|
||||
"s_warehouse": row.item_details["t_warehouse"],
|
||||
"t_warehouse": row.item_details["s_warehouse"],
|
||||
"item_code": row.item_details["rm_item_code"],
|
||||
"subcontracted_item": row.item_details["main_item_code"],
|
||||
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
|
||||
}
|
||||
"Purchase Order": {
|
||||
"doctype": "Subcontracting Order",
|
||||
"field_map": {},
|
||||
"field_no_map": ["total_qty", "total", "net_total"],
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
},
|
||||
},
|
||||
"Purchase Order Item": {
|
||||
"doctype": "Subcontracting Order Service Item",
|
||||
"field_map": {},
|
||||
"field_no_map": [],
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
)
|
||||
|
||||
target_doc.populate_items_table()
|
||||
|
||||
if target_doc.set_warehouse:
|
||||
for item in target_doc.items:
|
||||
item.warehouse = target_doc.set_warehouse
|
||||
else:
|
||||
source_doc = frappe.get_doc("Purchase Order", source_name)
|
||||
if source_doc.set_warehouse:
|
||||
for item in target_doc.items:
|
||||
item.warehouse = source_doc.set_warehouse
|
||||
else:
|
||||
for idx, item in enumerate(target_doc.items):
|
||||
item.warehouse = source_doc.items[idx].warehouse
|
||||
|
||||
return target_doc
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_subcontracting_order_created(po_name) -> bool:
|
||||
count = frappe.db.count(
|
||||
"Subcontracting Order", {"purchase_order": po_name, "status": ["not in", ["Draft", "Cancelled"]]}
|
||||
)
|
||||
|
||||
return True if count else False
|
||||
|
@ -22,6 +22,6 @@ def get_data():
|
||||
"label": _("Reference"),
|
||||
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
|
||||
},
|
||||
{"label": _("Sub-contracting"), "items": ["Stock Entry"]},
|
||||
{"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]},
|
||||
],
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Purchase Order');
|
@ -13,9 +13,6 @@ from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||
make_purchase_invoice as make_pi_from_po,
|
||||
)
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||
make_rm_stock_entry as make_subcontract_transfer_entry,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
@ -24,7 +21,6 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
make_purchase_invoice as make_pi_from_pr,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
|
||||
class TestPurchaseOrder(FrappeTestCase):
|
||||
@ -140,43 +136,6 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
# ordered qty decreases as ordered qty is 0 (deleted row)
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
|
||||
|
||||
def test_supplied_items_validations_on_po_update_after_submit(self):
|
||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1, qty=5, rate=100)
|
||||
item = po.items[0]
|
||||
|
||||
original_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
|
||||
|
||||
# Just update rate
|
||||
trans_item = [
|
||||
{
|
||||
"item_code": "_Test FG Item",
|
||||
"rate": 20,
|
||||
"qty": 5,
|
||||
"conversion_factor": 1.0,
|
||||
"docname": item.name,
|
||||
}
|
||||
]
|
||||
update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
|
||||
po.reload()
|
||||
|
||||
new_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
|
||||
self.assertEqual(set(original_supplied_items.keys()), set(new_supplied_items.keys()))
|
||||
|
||||
# Update qty to 2x
|
||||
trans_item[0]["qty"] *= 2
|
||||
update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
|
||||
po.reload()
|
||||
|
||||
new_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
|
||||
self.assertEqual(2 * sum(original_supplied_items.values()), sum(new_supplied_items.values()))
|
||||
|
||||
# Set transfer qty and attempt to update qty, shouldn't be allowed
|
||||
po.supplied_items[0].supplied_qty = 2
|
||||
po.supplied_items[0].db_update()
|
||||
trans_item[0]["qty"] *= 2
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
|
||||
|
||||
def test_update_child(self):
|
||||
mr = make_material_request(qty=10)
|
||||
po = make_purchase_order(mr.name)
|
||||
@ -426,31 +385,6 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
new_item_with_tax.delete()
|
||||
frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete()
|
||||
|
||||
def test_update_child_uom_conv_factor_change(self):
|
||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
|
||||
|
||||
trans_item = json.dumps(
|
||||
[
|
||||
{
|
||||
"item_code": po.get("items")[0].item_code,
|
||||
"rate": po.get("items")[0].rate,
|
||||
"qty": po.get("items")[0].qty,
|
||||
"uom": "_Test UOM 1",
|
||||
"conversion_factor": 2,
|
||||
"docname": po.get("items")[0].name,
|
||||
}
|
||||
]
|
||||
)
|
||||
update_child_qty_rate("Purchase Order", trans_item, po.name)
|
||||
po.reload()
|
||||
|
||||
total_reqd_qty_after_change = sum(
|
||||
d.get("required_qty") for d in po.as_dict().get("supplied_items")
|
||||
)
|
||||
|
||||
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
|
||||
|
||||
def test_update_qty(self):
|
||||
po = create_purchase_order()
|
||||
|
||||
@ -609,10 +543,6 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
)
|
||||
automatically_fetch_payment_terms(enable=0)
|
||||
|
||||
def test_subcontracting(self):
|
||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||
self.assertEqual(len(po.get("supplied_items")), 2)
|
||||
|
||||
def test_warehouse_company_validation(self):
|
||||
from erpnext.stock.utils import InvalidWarehouseCompany
|
||||
|
||||
@ -777,379 +707,6 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
pi.insert()
|
||||
self.assertTrue(pi.get("payment_schedule"))
|
||||
|
||||
def test_reserved_qty_subcontract_po(self):
|
||||
# Make stock available for raw materials
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
item_code="_Test Item Home Desktop 100",
|
||||
qty=30,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
bin1 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
# Submit PO
|
||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||
|
||||
bin2 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
||||
self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
|
||||
self.assertNotEqual(bin1.modified, bin2.modified)
|
||||
|
||||
# Create stock transfer
|
||||
rm_item = [
|
||||
{
|
||||
"item_code": "_Test FG Item",
|
||||
"rm_item_code": "_Test Item",
|
||||
"item_name": "_Test Item",
|
||||
"qty": 6,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"rate": 100,
|
||||
"amount": 600,
|
||||
"stock_uom": "Nos",
|
||||
}
|
||||
]
|
||||
rm_item_string = json.dumps(rm_item)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.to_warehouse = "_Test Warehouse 1 - _TC"
|
||||
se.save()
|
||||
se.submit()
|
||||
|
||||
bin3 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
# close PO
|
||||
po.update_status("Closed")
|
||||
bin4 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
# Re-open PO
|
||||
po.update_status("Submitted")
|
||||
bin5 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
item_code="_Test Item Home Desktop 100",
|
||||
qty=40,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
# make Purchase Receipt against PO
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
bin6 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
# Cancel PR
|
||||
pr.cancel()
|
||||
bin7 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
# Make Purchase Invoice
|
||||
pi = make_pi_from_po(po.name)
|
||||
pi.update_stock = 1
|
||||
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
bin8 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
# Cancel PR
|
||||
pi.cancel()
|
||||
bin9 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
# Cancel Stock Entry
|
||||
se.cancel()
|
||||
bin10 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
||||
|
||||
# Cancel PO
|
||||
po.reload()
|
||||
po.cancel()
|
||||
bin11 = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
def test_exploded_items_in_subcontracted(self):
|
||||
item_code = "_Test Subcontracted FG Item 11"
|
||||
make_subcontracted_item(item_code=item_code)
|
||||
|
||||
po = create_purchase_order(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
is_subcontracted=1,
|
||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||
include_exploded_items=1,
|
||||
)
|
||||
|
||||
name = frappe.db.get_value("BOM", {"item": item_code}, "name")
|
||||
bom = frappe.get_doc("BOM", name)
|
||||
|
||||
exploded_items = sorted(
|
||||
[d.item_code for d in bom.exploded_items if not d.get("sourced_by_supplier")]
|
||||
)
|
||||
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
||||
self.assertEqual(exploded_items, supplied_items)
|
||||
|
||||
po1 = create_purchase_order(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
is_subcontracted=1,
|
||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||
include_exploded_items=0,
|
||||
)
|
||||
|
||||
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
|
||||
bom_items = sorted([d.item_code for d in bom.items if not d.get("sourced_by_supplier")])
|
||||
|
||||
self.assertEqual(supplied_items1, bom_items)
|
||||
|
||||
def test_backflush_based_on_stock_entry(self):
|
||||
item_code = "_Test Subcontracted FG Item 1"
|
||||
make_subcontracted_item(item_code=item_code)
|
||||
make_item("Sub Contracted Raw Material 1", {"is_stock_item": 1, "is_sub_contracted_item": 1})
|
||||
|
||||
update_backflush_based_on("Material Transferred for Subcontract")
|
||||
|
||||
order_qty = 5
|
||||
po = create_purchase_order(
|
||||
item_code=item_code,
|
||||
qty=order_qty,
|
||||
is_subcontracted=1,
|
||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=100, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=10, basic_rate=100
|
||||
)
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
item_code="Sub Contracted Raw Material 1",
|
||||
qty=10,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
rm_items = [
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": "Sub Contracted Raw Material 1",
|
||||
"item_name": "_Test Item",
|
||||
"qty": 10,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": "_Test Item Home Desktop 100",
|
||||
"item_name": "_Test Item Home Desktop 100",
|
||||
"qty": 20,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": "Test Extra Item 1",
|
||||
"item_name": "Test Extra Item 1",
|
||||
"qty": 10,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": "Test Extra Item 2",
|
||||
"stock_uom": "Nos",
|
||||
"qty": 10,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_name": "Test Extra Item 2",
|
||||
},
|
||||
]
|
||||
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
|
||||
received_qty = 2
|
||||
# partial receipt
|
||||
pr.get("items")[0].qty = received_qty
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
transferred_items = sorted(
|
||||
[d.item_code for d in se.get("items") if se.purchase_order == po.name]
|
||||
)
|
||||
issued_items = sorted([d.rm_item_code for d in pr.get("supplied_items")])
|
||||
|
||||
self.assertEqual(transferred_items, issued_items)
|
||||
self.assertEqual(pr.get("items")[0].rm_supp_cost, 2000)
|
||||
|
||||
transferred_rm_map = frappe._dict()
|
||||
for item in rm_items:
|
||||
transferred_rm_map[item.get("rm_item_code")] = item
|
||||
|
||||
update_backflush_based_on("BOM")
|
||||
|
||||
def test_supplied_qty_against_subcontracted_po(self):
|
||||
item_code = "_Test Subcontracted FG Item 5"
|
||||
make_item("Sub Contracted Raw Material 4", {"is_stock_item": 1, "is_sub_contracted_item": 1})
|
||||
|
||||
make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"])
|
||||
|
||||
update_backflush_based_on("Material Transferred for Subcontract")
|
||||
|
||||
order_qty = 250
|
||||
po = create_purchase_order(
|
||||
item_code=item_code,
|
||||
qty=order_qty,
|
||||
is_subcontracted=1,
|
||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||
do_not_save=True,
|
||||
)
|
||||
|
||||
# Add same subcontracted items multiple times
|
||||
po.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item_code,
|
||||
"qty": order_qty,
|
||||
"schedule_date": add_days(nowdate(), 1),
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
},
|
||||
)
|
||||
|
||||
po.set_missing_values()
|
||||
po.submit()
|
||||
|
||||
# Material receipt entry for the raw materials which will be send to supplier
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
item_code="Sub Contracted Raw Material 4",
|
||||
qty=500,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
rm_items = [
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": "Sub Contracted Raw Material 4",
|
||||
"item_name": "_Test Item",
|
||||
"qty": 250,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos",
|
||||
"name": po.supplied_items[0].name,
|
||||
},
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": "Sub Contracted Raw Material 4",
|
||||
"item_name": "_Test Item",
|
||||
"qty": 250,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
]
|
||||
|
||||
# Raw Materials transfer entry from stores to supplier's warehouse
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.submit()
|
||||
|
||||
# Test po_detail field has value or not
|
||||
for item_row in se.items:
|
||||
self.assertEqual(item_row.po_detail, po.supplied_items[item_row.idx - 1].name)
|
||||
|
||||
po_doc = frappe.get_doc("Purchase Order", po.name)
|
||||
for row in po_doc.supplied_items:
|
||||
# Valid that whether transferred quantity is matching with supplied qty or not in the purchase order
|
||||
self.assertEqual(row.supplied_qty, 250.0)
|
||||
|
||||
update_backflush_based_on("BOM")
|
||||
|
||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
@ -1248,50 +805,6 @@ def make_pr_against_po(po, received_qty=0):
|
||||
return pr
|
||||
|
||||
|
||||
def make_subcontracted_item(**args):
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
if not frappe.db.exists("Item", args.item_code):
|
||||
make_item(
|
||||
args.item_code,
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"is_sub_contracted_item": 1,
|
||||
"has_batch_no": args.get("has_batch_no") or 0,
|
||||
},
|
||||
)
|
||||
|
||||
if not args.raw_materials:
|
||||
if not frappe.db.exists("Item", "Test Extra Item 1"):
|
||||
make_item(
|
||||
"Test Extra Item 1",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
},
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Item", "Test Extra Item 2"):
|
||||
make_item(
|
||||
"Test Extra Item 2",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
},
|
||||
)
|
||||
|
||||
args.raw_materials = ["_Test FG Item", "Test Extra Item 1"]
|
||||
|
||||
if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"):
|
||||
make_bom(item=args.item_code, raw_materials=args.get("raw_materials"))
|
||||
|
||||
|
||||
def update_backflush_based_on(based_on):
|
||||
doc = frappe.get_doc("Buying Settings")
|
||||
doc.backflush_raw_materials_of_subcontract_based_on = based_on
|
||||
doc.save()
|
||||
|
||||
|
||||
def get_same_items():
|
||||
return [
|
||||
{
|
||||
|
@ -1,38 +1,4 @@
|
||||
[
|
||||
{
|
||||
"advance_paid": 0.0,
|
||||
"buying_price_list": "_Test Price List",
|
||||
"company": "_Test Company",
|
||||
"conversion_rate": 1.0,
|
||||
"currency": "INR",
|
||||
"doctype": "Purchase Order",
|
||||
"base_grand_total": 5000.0,
|
||||
"grand_total": 5000.0,
|
||||
"is_subcontracted": 1,
|
||||
"naming_series": "_T-Purchase Order-",
|
||||
"base_net_total": 5000.0,
|
||||
"items": [
|
||||
{
|
||||
"base_amount": 5000.0,
|
||||
"conversion_factor": 1.0,
|
||||
"description": "_Test FG Item",
|
||||
"doctype": "Purchase Order Item",
|
||||
"item_code": "_Test FG Item",
|
||||
"item_name": "_Test FG Item",
|
||||
"parentfield": "items",
|
||||
"qty": 10.0,
|
||||
"rate": 500.0,
|
||||
"schedule_date": "2013-03-01",
|
||||
"stock_uom": "_Test UOM",
|
||||
"uom": "_Test UOM",
|
||||
"warehouse": "_Test Warehouse - _TC"
|
||||
}
|
||||
],
|
||||
"supplier": "_Test Supplier",
|
||||
"supplier_name": "_Test Supplier",
|
||||
"transaction_date": "2013-02-12",
|
||||
"schedule_date": "2013-02-13"
|
||||
},
|
||||
{
|
||||
"advance_paid": 0.0,
|
||||
"buying_price_list": "_Test Price List",
|
||||
|
@ -11,6 +11,8 @@
|
||||
"supplier_part_no",
|
||||
"item_name",
|
||||
"product_bundle",
|
||||
"fg_item",
|
||||
"fg_item_qty",
|
||||
"column_break_4",
|
||||
"schedule_date",
|
||||
"expected_delivery_date",
|
||||
@ -574,16 +576,18 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
@ -848,6 +852,22 @@
|
||||
"label": "Sales Order Packed Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finished Good Item",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"fieldname": "fg_item_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Finished Good Item Qty",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
|
@ -180,12 +180,20 @@ class RequestforQuotation(BuyingController):
|
||||
doc_args = self.as_dict()
|
||||
doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
|
||||
|
||||
# Get Contact Full Name
|
||||
supplier_name = None
|
||||
if data.get("contact"):
|
||||
contact_name = frappe.db.get_value(
|
||||
"Contact", data.get("contact"), ["first_name", "middle_name", "last_name"]
|
||||
)
|
||||
supplier_name = (" ").join(x for x in contact_name if x) # remove any blank values
|
||||
|
||||
args = {
|
||||
"update_password_link": update_password_link,
|
||||
"message": frappe.render_template(self.message_for_supplier, doc_args),
|
||||
"rfq_link": rfq_link,
|
||||
"user_fullname": full_name,
|
||||
"supplier_name": data.get("supplier_name"),
|
||||
"supplier_name": supplier_name or data.get("supplier_name"),
|
||||
"supplier_salutation": self.salutation or "Dear Mx.",
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
{% include "erpnext/regional/india/party.js" %}
|
||||
|
||||
erpnext.setup_gst_reminder_button('Supplier');
|
@ -14,32 +14,29 @@ frappe.query_reports["Subcontract Order Summary"] = {
|
||||
},
|
||||
{
|
||||
label: __("From Date"),
|
||||
fieldname:"from_date",
|
||||
fieldname: "from_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("To Date"),
|
||||
fieldname:"to_date",
|
||||
fieldname: "to_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Purchase Order"),
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order"
|
||||
},
|
||||
{
|
||||
label: __("Subcontract Order"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Link",
|
||||
options: "Purchase Order",
|
||||
get_query: function() {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
is_subcontracted: 1,
|
||||
company: frappe.query_report.get_filter_value('company')
|
||||
}
|
||||
}
|
||||
}
|
||||
fieldtype: "Data"
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
@ -15,7 +15,7 @@
|
||||
"name": "Subcontract Order Summary",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"ref_doctype": "Subcontracting Order",
|
||||
"report_name": "Subcontract Order Summary",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
|
@ -8,7 +8,7 @@ from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
columns = get_columns()
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
@ -20,43 +20,45 @@ def get_data(report_filters):
|
||||
|
||||
if orders:
|
||||
supplied_items = get_supplied_items(orders, report_filters)
|
||||
po_details = prepare_subcontracted_data(orders, supplied_items)
|
||||
get_subcontracted_data(po_details, data)
|
||||
order_details = prepare_subcontracted_data(orders, supplied_items)
|
||||
get_subcontracted_data(order_details, data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_subcontracted_orders(report_filters):
|
||||
fields = [
|
||||
"`tabPurchase Order Item`.`parent` as po_id",
|
||||
"`tabPurchase Order Item`.`item_code`",
|
||||
"`tabPurchase Order Item`.`item_name`",
|
||||
"`tabPurchase Order Item`.`qty`",
|
||||
"`tabPurchase Order Item`.`name`",
|
||||
"`tabPurchase Order Item`.`received_qty`",
|
||||
"`tabPurchase Order`.`status`",
|
||||
f"`tab{report_filters.order_type} Item`.`parent` as order_id",
|
||||
f"`tab{report_filters.order_type} Item`.`item_code`",
|
||||
f"`tab{report_filters.order_type} Item`.`item_name`",
|
||||
f"`tab{report_filters.order_type} Item`.`qty`",
|
||||
f"`tab{report_filters.order_type} Item`.`name`",
|
||||
f"`tab{report_filters.order_type} Item`.`received_qty`",
|
||||
f"`tab{report_filters.order_type}`.`status`",
|
||||
]
|
||||
|
||||
filters = get_filters(report_filters)
|
||||
|
||||
return frappe.get_all("Purchase Order", fields=fields, filters=filters) or []
|
||||
return frappe.get_all(report_filters.order_type, fields=fields, filters=filters) or []
|
||||
|
||||
|
||||
def get_filters(report_filters):
|
||||
filters = [
|
||||
["Purchase Order", "docstatus", "=", 1],
|
||||
["Purchase Order", "is_subcontracted", "=", 1],
|
||||
[report_filters.order_type, "docstatus", "=", 1],
|
||||
[
|
||||
"Purchase Order",
|
||||
report_filters.order_type,
|
||||
"transaction_date",
|
||||
"between",
|
||||
(report_filters.from_date, report_filters.to_date),
|
||||
],
|
||||
]
|
||||
|
||||
if report_filters.order_type == "Purchase Order":
|
||||
filters.append(["Purchase Order", "is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
for field in ["name", "company"]:
|
||||
if report_filters.get(field):
|
||||
filters.append(["Purchase Order", field, "=", report_filters.get(field)])
|
||||
filters.append([report_filters.order_type, field, "=", report_filters.get(field)])
|
||||
|
||||
return filters
|
||||
|
||||
@ -77,10 +79,15 @@ def get_supplied_items(orders, report_filters):
|
||||
"reference_name",
|
||||
]
|
||||
|
||||
filters = {"parent": ("in", [d.po_id for d in orders]), "docstatus": 1}
|
||||
filters = {"parent": ("in", [d.order_id for d in orders]), "docstatus": 1}
|
||||
|
||||
supplied_items = {}
|
||||
for row in frappe.get_all("Purchase Order Item Supplied", fields=fields, filters=filters):
|
||||
supplied_items_table = (
|
||||
"Purchase Order Item Supplied"
|
||||
if report_filters.order_type == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
for row in frappe.get_all(supplied_items_table, fields=fields, filters=filters):
|
||||
new_key = (row.parent, row.reference_name, row.main_item_code)
|
||||
|
||||
supplied_items.setdefault(new_key, []).append(row)
|
||||
@ -89,24 +96,24 @@ def get_supplied_items(orders, report_filters):
|
||||
|
||||
|
||||
def prepare_subcontracted_data(orders, supplied_items):
|
||||
po_details = {}
|
||||
order_details = {}
|
||||
for row in orders:
|
||||
key = (row.po_id, row.name, row.item_code)
|
||||
if key not in po_details:
|
||||
po_details.setdefault(key, frappe._dict({"po_item": row, "supplied_items": []}))
|
||||
key = (row.order_id, row.name, row.item_code)
|
||||
if key not in order_details:
|
||||
order_details.setdefault(key, frappe._dict({"order_item": row, "supplied_items": []}))
|
||||
|
||||
details = po_details[key]
|
||||
details = order_details[key]
|
||||
|
||||
if supplied_items.get(key):
|
||||
for supplied_item in supplied_items[key]:
|
||||
details["supplied_items"].append(supplied_item)
|
||||
|
||||
return po_details
|
||||
return order_details
|
||||
|
||||
|
||||
def get_subcontracted_data(po_details, data):
|
||||
for key, details in po_details.items():
|
||||
res = details.po_item
|
||||
def get_subcontracted_data(order_details, data):
|
||||
for key, details in order_details.items():
|
||||
res = details.order_item
|
||||
for index, row in enumerate(details.supplied_items):
|
||||
if index != 0:
|
||||
res = {}
|
||||
@ -115,13 +122,13 @@ def get_subcontracted_data(po_details, data):
|
||||
data.append(res)
|
||||
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"fieldname": "po_id",
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldname": "order_id",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"options": filters.order_type,
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
|
||||
|
@ -4,6 +4,13 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Item To Be Received"] = {
|
||||
"filters": [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order"
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
@ -13,7 +13,7 @@
|
||||
"name": "Subcontracted Item To Be Received",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"ref_doctype": "Subcontracting Order",
|
||||
"report_name": "Subcontracted Item To Be Received",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
|
@ -11,18 +11,18 @@ def execute(filters=None):
|
||||
frappe.msgprint(_("To Date must be greater than From Date"))
|
||||
|
||||
data = []
|
||||
columns = get_columns()
|
||||
columns = get_columns(filters)
|
||||
get_data(data, filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "purchase_order",
|
||||
"options": "Purchase Order",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 150,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150},
|
||||
@ -57,14 +57,14 @@ def get_columns():
|
||||
|
||||
|
||||
def get_data(data, filters):
|
||||
po = get_po(filters)
|
||||
po_name = [v.name for v in po]
|
||||
sub_items = get_purchase_order_item_supplied(po_name)
|
||||
for item in sub_items:
|
||||
for order in po:
|
||||
orders = get_subcontract_orders(filters)
|
||||
orders_name = [order.name for order in orders]
|
||||
subcontracted_items = get_subcontract_order_supplied_item(filters.order_type, orders_name)
|
||||
for item in subcontracted_items:
|
||||
for order in orders:
|
||||
if order.name == item.parent and item.received_qty < item.qty:
|
||||
row = {
|
||||
"purchase_order": item.parent,
|
||||
"subcontract_order": item.parent,
|
||||
"date": order.transaction_date,
|
||||
"supplier": order.supplier,
|
||||
"fg_item_code": item.item_code,
|
||||
@ -76,22 +76,25 @@ def get_data(data, filters):
|
||||
data.append(row)
|
||||
|
||||
|
||||
def get_po(filters):
|
||||
def get_subcontract_orders(filters):
|
||||
record_filters = [
|
||||
["is_subcontracted", "=", 1],
|
||||
["supplier", "=", filters.supplier],
|
||||
["transaction_date", "<=", filters.to_date],
|
||||
["transaction_date", ">=", filters.from_date],
|
||||
["docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append(["is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
return frappe.get_all(
|
||||
"Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"]
|
||||
filters.order_type, filters=record_filters, fields=["name", "transaction_date", "supplier"]
|
||||
)
|
||||
|
||||
|
||||
def get_purchase_order_item_supplied(po):
|
||||
def get_subcontract_order_supplied_item(order_type, orders):
|
||||
return frappe.get_all(
|
||||
"Purchase Order Item",
|
||||
filters=[("parent", "IN", po)],
|
||||
f"{order_type} Item",
|
||||
filters=[("parent", "IN", orders)],
|
||||
fields=["parent", "item_code", "item_name", "qty", "received_qty"],
|
||||
)
|
||||
|
@ -7,18 +7,35 @@
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_to_be_received import (
|
||||
execute,
|
||||
)
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
get_subcontracting_order,
|
||||
make_service_item,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
make_subcontracting_receipt,
|
||||
)
|
||||
|
||||
|
||||
class TestSubcontractedItemToBeReceived(FrappeTestCase):
|
||||
def test_pending_and_received_qty(self):
|
||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||
transfer_param = []
|
||||
make_service_item("Subcontracted Service Item 1")
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 1",
|
||||
"qty": 10,
|
||||
"rate": 500,
|
||||
"fg_item": "_Test FG Item",
|
||||
"fg_item_qty": 10,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(
|
||||
service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
|
||||
)
|
||||
@ -28,28 +45,28 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
|
||||
qty=100,
|
||||
basic_rate=100,
|
||||
)
|
||||
make_purchase_receipt_against_po(po.name)
|
||||
po.reload()
|
||||
make_subcontracting_receipt_against_sco(sco.name)
|
||||
sco.reload()
|
||||
col, data = execute(
|
||||
filters=frappe._dict(
|
||||
{
|
||||
"supplier": po.supplier,
|
||||
"order_type": "Subcontracting Order",
|
||||
"supplier": sco.supplier,
|
||||
"from_date": frappe.utils.get_datetime(
|
||||
frappe.utils.add_to_date(po.transaction_date, days=-10)
|
||||
frappe.utils.add_to_date(sco.transaction_date, days=-10)
|
||||
),
|
||||
"to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)),
|
||||
"to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(sco.transaction_date, days=10)),
|
||||
}
|
||||
)
|
||||
)
|
||||
self.assertEqual(data[0]["pending_qty"], 5)
|
||||
self.assertEqual(data[0]["received_qty"], 5)
|
||||
self.assertEqual(data[0]["purchase_order"], po.name)
|
||||
self.assertEqual(data[0]["supplier"], po.supplier)
|
||||
self.assertEqual(data[0]["subcontract_order"], sco.name)
|
||||
self.assertEqual(data[0]["supplier"], sco.supplier)
|
||||
|
||||
|
||||
def make_purchase_receipt_against_po(po, quantity=5):
|
||||
pr = make_purchase_receipt(po)
|
||||
pr.items[0].qty = quantity
|
||||
pr.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
def make_subcontracting_receipt_against_sco(sco, quantity=5):
|
||||
scr = make_subcontracting_receipt(sco)
|
||||
scr.items[0].qty = quantity
|
||||
scr.insert()
|
||||
scr.submit()
|
||||
|
@ -4,6 +4,13 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
|
||||
"filters": [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order"
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
@ -13,7 +13,7 @@
|
||||
"name": "Subcontracted Raw Materials To Be Transferred",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"ref_doctype": "Subcontracting Order",
|
||||
"report_name": "Subcontracted Raw Materials To Be Transferred",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
|
@ -10,19 +10,19 @@ def execute(filters=None):
|
||||
if filters.from_date >= filters.to_date:
|
||||
frappe.msgprint(_("To Date must be greater than From Date"))
|
||||
|
||||
columns = get_columns()
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data or []
|
||||
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "purchase_order",
|
||||
"options": "Purchase Order",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 200,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150},
|
||||
@ -46,10 +46,10 @@ def get_columns():
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
po_rm_item_details = get_po_items_to_supply(filters)
|
||||
order_rm_item_details = get_order_items_to_supply(filters)
|
||||
|
||||
data = []
|
||||
for row in po_rm_item_details:
|
||||
for row in order_rm_item_details:
|
||||
transferred_qty = row.get("transferred_qty") or 0
|
||||
if transferred_qty < row.get("reqd_qty", 0):
|
||||
pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty)
|
||||
@ -59,23 +59,33 @@ def get_data(filters):
|
||||
return data
|
||||
|
||||
|
||||
def get_po_items_to_supply(filters):
|
||||
def get_order_items_to_supply(filters):
|
||||
supplied_items_table = (
|
||||
"Purchase Order Item Supplied"
|
||||
if filters.order_type == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
|
||||
record_filters = [
|
||||
[filters.order_type, "per_received", "<", "100"],
|
||||
[filters.order_type, "supplier", "=", filters.supplier],
|
||||
[filters.order_type, "transaction_date", "<=", filters.to_date],
|
||||
[filters.order_type, "transaction_date", ">=", filters.from_date],
|
||||
[filters.order_type, "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append([filters.order_type, "is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
return frappe.db.get_all(
|
||||
"Purchase Order",
|
||||
filters.order_type,
|
||||
fields=[
|
||||
"name as purchase_order",
|
||||
"name as subcontract_order",
|
||||
"transaction_date as date",
|
||||
"supplier as supplier",
|
||||
"`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code",
|
||||
"`tabPurchase Order Item Supplied`.required_qty as reqd_qty",
|
||||
"`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty",
|
||||
],
|
||||
filters=[
|
||||
["Purchase Order", "per_received", "<", "100"],
|
||||
["Purchase Order", "is_subcontracted", "=", 1],
|
||||
["Purchase Order", "supplier", "=", filters.supplier],
|
||||
["Purchase Order", "transaction_date", "<=", filters.to_date],
|
||||
["Purchase Order", "transaction_date", ">=", filters.from_date],
|
||||
["Purchase Order", "docstatus", "=", 1],
|
||||
f"`tab{supplied_items_table}`.rm_item_code as rm_item_code",
|
||||
f"`tab{supplied_items_table}`.required_qty as reqd_qty",
|
||||
f"`tab{supplied_items_table}`.supplied_qty as transferred_qty",
|
||||
],
|
||||
filters=record_filters,
|
||||
)
|
||||
|
@ -3,24 +3,34 @@
|
||||
# Compiled at: 2019-05-06 10:24:35
|
||||
# Decompiled by https://python-decompiler.com
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import (
|
||||
execute,
|
||||
)
|
||||
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
get_subcontracting_order,
|
||||
make_service_item,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
|
||||
class TestSubcontractedItemToBeTransferred(FrappeTestCase):
|
||||
def test_pending_and_transferred_qty(self):
|
||||
po = create_purchase_order(
|
||||
item_code="_Test FG Item", is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||
)
|
||||
make_service_item("Subcontracted Service Item 1")
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 1",
|
||||
"qty": 10,
|
||||
"rate": 500,
|
||||
"fg_item": "_Test FG Item",
|
||||
"fg_item_qty": 10,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
|
||||
# Material Receipt of RMs
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100)
|
||||
@ -28,50 +38,48 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase):
|
||||
item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=100, basic_rate=100
|
||||
)
|
||||
|
||||
se = transfer_subcontracted_raw_materials(po)
|
||||
transfer_subcontracted_raw_materials(sco)
|
||||
|
||||
col, data = execute(
|
||||
filters=frappe._dict(
|
||||
{
|
||||
"supplier": po.supplier,
|
||||
"order_type": "Subcontracting Order",
|
||||
"supplier": sco.supplier,
|
||||
"from_date": frappe.utils.get_datetime(
|
||||
frappe.utils.add_to_date(po.transaction_date, days=-10)
|
||||
frappe.utils.add_to_date(sco.transaction_date, days=-10)
|
||||
),
|
||||
"to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10)),
|
||||
"to_date": frappe.utils.get_datetime(frappe.utils.add_to_date(sco.transaction_date, days=10)),
|
||||
}
|
||||
)
|
||||
)
|
||||
po.reload()
|
||||
sco.reload()
|
||||
|
||||
po_data = [row for row in data if row.get("purchase_order") == po.name]
|
||||
sco_data = [row for row in data if row.get("subcontract_order") == sco.name]
|
||||
# Alphabetically sort to be certain of order
|
||||
po_data = sorted(po_data, key=lambda i: i["rm_item_code"])
|
||||
sco_data = sorted(sco_data, key=lambda i: i["rm_item_code"])
|
||||
|
||||
self.assertEqual(len(po_data), 2)
|
||||
self.assertEqual(po_data[0]["purchase_order"], po.name)
|
||||
self.assertEqual(len(sco_data), 2)
|
||||
self.assertEqual(sco_data[0]["subcontract_order"], sco.name)
|
||||
|
||||
self.assertEqual(po_data[0]["rm_item_code"], "_Test Item")
|
||||
self.assertEqual(po_data[0]["p_qty"], 8)
|
||||
self.assertEqual(po_data[0]["transferred_qty"], 2)
|
||||
self.assertEqual(sco_data[0]["rm_item_code"], "_Test Item")
|
||||
self.assertEqual(sco_data[0]["p_qty"], 8)
|
||||
self.assertEqual(sco_data[0]["transferred_qty"], 2)
|
||||
|
||||
self.assertEqual(po_data[1]["rm_item_code"], "_Test Item Home Desktop 100")
|
||||
self.assertEqual(po_data[1]["p_qty"], 19)
|
||||
self.assertEqual(po_data[1]["transferred_qty"], 1)
|
||||
|
||||
se.cancel()
|
||||
po.cancel()
|
||||
self.assertEqual(sco_data[1]["rm_item_code"], "_Test Item Home Desktop 100")
|
||||
self.assertEqual(sco_data[1]["p_qty"], 19)
|
||||
self.assertEqual(sco_data[1]["transferred_qty"], 1)
|
||||
|
||||
|
||||
def transfer_subcontracted_raw_materials(po):
|
||||
# Order of supplied items fetched in PO is flaky
|
||||
def transfer_subcontracted_raw_materials(sco):
|
||||
# Order of supplied items fetched in SCO is flaky
|
||||
transfer_qty_map = {"_Test Item": 2, "_Test Item Home Desktop 100": 1}
|
||||
|
||||
item_1 = po.supplied_items[0].rm_item_code
|
||||
item_2 = po.supplied_items[1].rm_item_code
|
||||
item_1 = sco.supplied_items[0].rm_item_code
|
||||
item_2 = sco.supplied_items[1].rm_item_code
|
||||
|
||||
rm_item = [
|
||||
rm_items = [
|
||||
{
|
||||
"name": po.supplied_items[0].name,
|
||||
"name": sco.supplied_items[0].name,
|
||||
"item_code": item_1,
|
||||
"rm_item_code": item_1,
|
||||
"item_name": item_1,
|
||||
@ -82,7 +90,7 @@ def transfer_subcontracted_raw_materials(po):
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
{
|
||||
"name": po.supplied_items[1].name,
|
||||
"name": sco.supplied_items[1].name,
|
||||
"item_code": item_2,
|
||||
"rm_item_code": item_2,
|
||||
"item_name": item_2,
|
||||
@ -93,8 +101,7 @@ def transfer_subcontracted_raw_materials(po):
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
]
|
||||
rm_item_string = json.dumps(rm_item)
|
||||
se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
|
||||
se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
|
||||
se.from_warehouse = "_Test Warehouse - _TC"
|
||||
se.to_warehouse = "_Test Warehouse - _TC"
|
||||
se.stock_entry_type = "Send to Subcontractor"
|
||||
|
@ -1472,8 +1472,15 @@ class AccountsController(TransactionBase):
|
||||
self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to")
|
||||
)
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
allow_multi_currency_invoices_against_single_party_account = frappe.db.get_singles_value(
|
||||
"Accounts Settings", "allow_multi_currency_invoices_against_single_party_account"
|
||||
)
|
||||
|
||||
if not party_gle_currency and (party_account_currency != self.currency):
|
||||
if (
|
||||
not party_gle_currency
|
||||
and (party_account_currency != self.currency)
|
||||
and not allow_multi_currency_invoices_against_single_party_account
|
||||
):
|
||||
frappe.throw(
|
||||
_("Party Account {0} currency ({1}) and document currency ({2}) should be same").format(
|
||||
frappe.bold(party_account), party_account_currency, self.currency
|
||||
@ -2709,10 +2716,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
parent.update_ordered_qty()
|
||||
parent.update_ordered_and_reserved_qty()
|
||||
parent.update_receiving_percentage()
|
||||
if parent.is_subcontracted:
|
||||
if parent.is_old_subcontracting_flow:
|
||||
if should_update_supplied_items(parent):
|
||||
parent.update_reserved_qty_for_subcontract()
|
||||
parent.create_raw_materials_supplied("supplied_items")
|
||||
parent.create_raw_materials_supplied()
|
||||
parent.save()
|
||||
else: # Sales Order
|
||||
parent.validate_warehouse()
|
||||
|
@ -11,8 +11,7 @@ from erpnext.accounts.doctype.budget.budget import validate_expense_against_budg
|
||||
from erpnext.accounts.party import get_party_details
|
||||
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
|
||||
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.controllers.subcontracting import Subcontracting
|
||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
@ -21,7 +20,7 @@ class QtyMismatchError(ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class BuyingController(StockController, Subcontracting):
|
||||
class BuyingController(SubcontractingController):
|
||||
def __setup__(self):
|
||||
self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"]
|
||||
|
||||
@ -55,7 +54,8 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
self.create_raw_materials_supplied("supplied_items")
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.create_raw_materials_supplied()
|
||||
self.set_landed_cost_voucher_amount()
|
||||
|
||||
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
@ -256,13 +256,18 @@ class BuyingController(StockController, Subcontracting):
|
||||
)
|
||||
|
||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount
|
||||
+ item.item_tax_amount
|
||||
+ item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount
|
||||
+ item.item_tax_amount
|
||||
+ item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
@ -300,7 +305,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate"))
|
||||
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||
else:
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
|
||||
|
||||
@ -317,76 +322,25 @@ class BuyingController(StockController, Subcontracting):
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
for d in self.get("supplied_items"):
|
||||
if d.reference_name == item_row_id:
|
||||
if reset_outgoing_rate and frappe.get_cached_value("Item", d.rm_item_code, "is_stock_item"):
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * d.consumed_qty,
|
||||
"serial_no": d.serial_no,
|
||||
"batch_no": d.batch_no,
|
||||
}
|
||||
)
|
||||
|
||||
if rate > 0:
|
||||
d.rate = rate
|
||||
|
||||
d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
|
||||
supplied_items_cost += flt(d.amount)
|
||||
|
||||
return supplied_items_cost
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if self.is_subcontracted:
|
||||
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
|
||||
|
||||
for item in self.get("items"):
|
||||
if item in self.sub_contracted_items and not item.bom:
|
||||
frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
|
||||
|
||||
if self.doctype != "Purchase Order":
|
||||
return
|
||||
|
||||
for row in self.get("supplied_items"):
|
||||
if not row.reserve_warehouse:
|
||||
msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
|
||||
frappe.throw(_(msg))
|
||||
else:
|
||||
for item in self.get("items"):
|
||||
if item.bom:
|
||||
if item.get("bom"):
|
||||
item.bom = None
|
||||
|
||||
def create_raw_materials_supplied(self, raw_material_table):
|
||||
if self.is_subcontracted:
|
||||
self.set_materials_for_subcontracted_items(raw_material_table)
|
||||
|
||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
for item in self.get("items"):
|
||||
item.rm_supp_cost = 0.0
|
||||
|
||||
if not self.is_subcontracted and self.get("supplied_items"):
|
||||
self.set("supplied_items", [])
|
||||
|
||||
@property
|
||||
def sub_contracted_items(self):
|
||||
if not hasattr(self, "_sub_contracted_items"):
|
||||
self._sub_contracted_items = []
|
||||
item_codes = list(set(item.item_code for item in self.get("items")))
|
||||
if item_codes:
|
||||
items = frappe.get_all(
|
||||
"Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1}
|
||||
)
|
||||
self._sub_contracted_items = [item.name for item in items]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
def set_qty_as_per_stock_uom(self):
|
||||
for d in self.get("items"):
|
||||
if d.meta.get_field("stock_qty"):
|
||||
@ -510,7 +464,9 @@ class BuyingController(StockController, Subcontracting):
|
||||
sle.update(
|
||||
{
|
||||
"incoming_rate": incoming_rate,
|
||||
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0,
|
||||
"recalculate_rate": 1
|
||||
if (self.is_subcontracted and (d.bom or d.fg_item)) or d.from_warehouse
|
||||
else 0,
|
||||
}
|
||||
)
|
||||
sl_entries.append(sle)
|
||||
@ -538,7 +494,8 @@ class BuyingController(StockController, Subcontracting):
|
||||
)
|
||||
)
|
||||
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
@ -565,26 +522,9 @@ class BuyingController(StockController, Subcontracting):
|
||||
)
|
||||
|
||||
po_obj.update_ordered_qty(po_item_rows)
|
||||
if self.is_subcontracted:
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
po_obj.update_reserved_qty_for_subcontract()
|
||||
|
||||
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
|
||||
if hasattr(self, "supplied_items"):
|
||||
for d in self.get("supplied_items"):
|
||||
# negative quantity is passed, as raw material qty has to be decreased
|
||||
# when PR is submitted and it has to be increased when PR is cancelled
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"actual_qty": -1 * flt(d.consumed_qty),
|
||||
"dependant_sle_voucher_detail_no": d.reference_name,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
if self.get("is_return"):
|
||||
return
|
||||
@ -808,7 +748,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
if self.doctype == "Material Request":
|
||||
return
|
||||
|
||||
if hasattr(self, "is_subcontracted") and self.is_subcontracted:
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
|
||||
else:
|
||||
validate_item_type(self, "is_purchase_item", "purchase")
|
||||
|
@ -77,7 +77,7 @@ def validate_returned_items(doc):
|
||||
if doc.doctype != "Purchase Invoice":
|
||||
select_fields += ",serial_no, batch_no"
|
||||
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
select_fields += ",rejected_qty, received_qty"
|
||||
|
||||
for d in frappe.db.sql(
|
||||
@ -161,7 +161,7 @@ def validate_returned_items(doc):
|
||||
|
||||
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
|
||||
fields = ["stock_qty"]
|
||||
if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
|
||||
fields.extend(["received_qty", "rejected_qty"])
|
||||
|
||||
already_returned_data = already_returned_items.get(args.item_code) or {}
|
||||
@ -224,7 +224,7 @@ def get_ref_item_dict(valid_items, ref_item_row):
|
||||
if ref_item_row.get("rate", 0) > item_dict["rate"]:
|
||||
item_dict["rate"] = ref_item_row.get("rate", 0)
|
||||
|
||||
if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
item_dict["received_qty"] += ref_item_row.received_qty
|
||||
item_dict["rejected_qty"] += ref_item_row.rejected_qty
|
||||
|
||||
@ -239,7 +239,7 @@ def get_ref_item_dict(valid_items, ref_item_row):
|
||||
|
||||
def get_already_returned_items(doc):
|
||||
column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty"
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
|
||||
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
|
||||
|
||||
@ -281,17 +281,21 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
|
||||
child_doctype = doctype + " Item"
|
||||
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
|
||||
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"):
|
||||
party_type = "supplier"
|
||||
else:
|
||||
party_type = "customer"
|
||||
|
||||
fields = [
|
||||
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype),
|
||||
]
|
||||
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
if doctype != "Subcontracting Receipt":
|
||||
fields += [
|
||||
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype),
|
||||
]
|
||||
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"):
|
||||
fields += [
|
||||
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
|
||||
@ -342,7 +346,7 @@ def make_return_doc(doctype: str, source_name: str, 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
|
||||
|
||||
@ -381,8 +385,11 @@ def make_return_doc(doctype: str, source_name: str, 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
|
||||
@ -393,7 +400,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
if serial_nos:
|
||||
target_doc.serial_no = "\n".join(serial_nos)
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||
)
|
||||
@ -405,15 +412,24 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
|
||||
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
|
||||
target_doc.received_stock_qty = -1 * flt(
|
||||
source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0)
|
||||
)
|
||||
if hasattr(target_doc, "stock_qty"):
|
||||
target_doc.stock_qty = -1 * flt(
|
||||
source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0)
|
||||
)
|
||||
target_doc.received_stock_qty = -1 * flt(
|
||||
source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0)
|
||||
)
|
||||
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
target_doc.purchase_order_item = source_doc.purchase_order_item
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.purchase_receipt_item = source_doc.name
|
||||
if doctype == "Subcontracting Receipt":
|
||||
target_doc.subcontracting_order = source_doc.subcontracting_order
|
||||
target_doc.subcontracting_order_item = source_doc.subcontracting_order_item
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.subcontracting_receipt_item = source_doc.name
|
||||
else:
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
target_doc.purchase_order_item = source_doc.purchase_order_item
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.purchase_receipt_item = source_doc.name
|
||||
|
||||
elif doctype == "Purchase Invoice":
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
@ -525,7 +541,7 @@ def get_rate_for_return(
|
||||
item_row,
|
||||
)
|
||||
|
||||
if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||
if voucher_type in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"):
|
||||
select_field = "incoming_rate"
|
||||
else:
|
||||
select_field = "abs(stock_value_difference / actual_qty)"
|
||||
@ -560,6 +576,7 @@ def get_return_against_item_fields(voucher_type):
|
||||
"Purchase Invoice": "purchase_invoice_item",
|
||||
"Delivery Note": "dn_detail",
|
||||
"Sales Invoice": "sales_invoice_item",
|
||||
"Subcontracting Receipt": "subcontracting_receipt_item",
|
||||
}
|
||||
return return_against_item_fields[voucher_type]
|
||||
|
||||
|
@ -1,469 +0,0 @@
|
||||
import copy
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, get_link_to_form
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
||||
class Subcontracting:
|
||||
def set_materials_for_subcontracted_items(self, raw_material_table):
|
||||
if self.doctype == "Purchase Invoice" and not self.update_stock:
|
||||
return
|
||||
|
||||
self.raw_material_table = raw_material_table
|
||||
self.__identify_change_in_item_table()
|
||||
self.__prepare_supplied_items()
|
||||
self.__validate_supplied_items()
|
||||
|
||||
def __prepare_supplied_items(self):
|
||||
self.initialized_fields()
|
||||
self.__get_purchase_orders()
|
||||
self.__get_pending_qty_to_receive()
|
||||
self.get_available_materials()
|
||||
self.__remove_changed_rows()
|
||||
self.__set_supplied_items()
|
||||
|
||||
def initialized_fields(self):
|
||||
self.available_materials = frappe._dict()
|
||||
self.__transferred_items = frappe._dict()
|
||||
self.alternative_item_details = frappe._dict()
|
||||
self.__get_backflush_based_on()
|
||||
|
||||
def __get_backflush_based_on(self):
|
||||
self.backflush_based_on = frappe.db.get_single_value(
|
||||
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
|
||||
)
|
||||
|
||||
def __get_purchase_orders(self):
|
||||
self.purchase_orders = []
|
||||
|
||||
if self.doctype == "Purchase Order":
|
||||
return
|
||||
|
||||
self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
|
||||
|
||||
def __identify_change_in_item_table(self):
|
||||
self.__changed_name = []
|
||||
self.__reference_name = []
|
||||
|
||||
if self.doctype == "Purchase Order" or self.is_new():
|
||||
self.set(self.raw_material_table, [])
|
||||
return
|
||||
|
||||
item_dict = self.__get_data_before_save()
|
||||
if not item_dict:
|
||||
return True
|
||||
|
||||
for n_row in self.items:
|
||||
self.__reference_name.append(n_row.name)
|
||||
if (n_row.name not in item_dict) or (n_row.item_code, n_row.qty) != item_dict[n_row.name]:
|
||||
self.__changed_name.append(n_row.name)
|
||||
|
||||
if item_dict.get(n_row.name):
|
||||
del item_dict[n_row.name]
|
||||
|
||||
self.__changed_name.extend(item_dict.keys())
|
||||
|
||||
def __get_data_before_save(self):
|
||||
item_dict = {}
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self._doc_before_save:
|
||||
for row in self._doc_before_save.get("items"):
|
||||
item_dict[row.name] = (row.item_code, row.qty)
|
||||
|
||||
return item_dict
|
||||
|
||||
def get_available_materials(self):
|
||||
"""Get the available raw materials which has been transferred to the supplier.
|
||||
available_materials = {
|
||||
(item_code, subcontracted_item, purchase_order): {
|
||||
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
|
||||
}
|
||||
}
|
||||
"""
|
||||
if not self.purchase_orders:
|
||||
return
|
||||
|
||||
for row in self.__get_transferred_items():
|
||||
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
|
||||
|
||||
if key not in self.available_materials:
|
||||
self.available_materials.setdefault(
|
||||
key,
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": 0,
|
||||
"serial_no": [],
|
||||
"batch_no": defaultdict(float),
|
||||
"item_details": row,
|
||||
"po_details": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
details = self.available_materials[key]
|
||||
details.qty += row.qty
|
||||
details.po_details.append(row.po_detail)
|
||||
|
||||
if row.serial_no:
|
||||
details.serial_no.extend(get_serial_nos(row.serial_no))
|
||||
|
||||
if row.batch_no:
|
||||
details.batch_no[row.batch_no] += row.qty
|
||||
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
self.__update_consumed_materials(doctype)
|
||||
|
||||
def __update_consumed_materials(self, doctype, return_consumed_items=False):
|
||||
"""Deduct the consumed materials from the available materials."""
|
||||
|
||||
pr_items = self.__get_received_items(doctype)
|
||||
if not pr_items:
|
||||
return ([], {}) if return_consumed_items else None
|
||||
|
||||
pr_items = {d.name: d.get(self.get("po_field") or "purchase_order") for d in pr_items}
|
||||
consumed_materials = self.__get_consumed_items(doctype, pr_items.keys())
|
||||
|
||||
if return_consumed_items:
|
||||
return (consumed_materials, pr_items)
|
||||
|
||||
for row in consumed_materials:
|
||||
key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
|
||||
if not self.available_materials.get(key):
|
||||
continue
|
||||
|
||||
self.available_materials[key]["qty"] -= row.consumed_qty
|
||||
if row.serial_no:
|
||||
self.available_materials[key]["serial_no"] = list(
|
||||
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
|
||||
)
|
||||
|
||||
if row.batch_no:
|
||||
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
|
||||
|
||||
def __get_transferred_items(self):
|
||||
fields = ["`tabStock Entry`.`purchase_order`"]
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"subcontracted_item": "main_item_code",
|
||||
"basic_rate": "rate",
|
||||
}
|
||||
|
||||
child_table_fields = [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"description",
|
||||
"qty",
|
||||
"basic_rate",
|
||||
"amount",
|
||||
"serial_no",
|
||||
"uom",
|
||||
"subcontracted_item",
|
||||
"stock_uom",
|
||||
"batch_no",
|
||||
"conversion_factor",
|
||||
"s_warehouse",
|
||||
"t_warehouse",
|
||||
"item_group",
|
||||
"po_detail",
|
||||
]
|
||||
|
||||
if self.backflush_based_on == "BOM":
|
||||
child_table_fields.append("original_item")
|
||||
|
||||
for field in child_table_fields:
|
||||
fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}")
|
||||
|
||||
filters = [
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["Stock Entry", "purpose", "=", "Send to Subcontractor"],
|
||||
["Stock Entry", "purchase_order", "in", self.purchase_orders],
|
||||
]
|
||||
|
||||
return frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||
|
||||
def __get_received_items(self, doctype):
|
||||
fields = []
|
||||
self.po_field = "purchase_order"
|
||||
|
||||
for field in ["name", self.po_field, "parent"]:
|
||||
fields.append(f"`tab{doctype} Item`.`{field}`")
|
||||
|
||||
filters = [
|
||||
[doctype, "docstatus", "=", 1],
|
||||
[f"{doctype} Item", self.po_field, "in", self.purchase_orders],
|
||||
]
|
||||
if doctype == "Purchase Invoice":
|
||||
filters.append(["Purchase Invoice", "update_stock", "=", 1])
|
||||
|
||||
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
||||
|
||||
def __get_consumed_items(self, doctype, pr_items):
|
||||
return frappe.get_all(
|
||||
"Purchase Receipt Item Supplied",
|
||||
fields=[
|
||||
"serial_no",
|
||||
"rm_item_code",
|
||||
"reference_name",
|
||||
"batch_no",
|
||||
"consumed_qty",
|
||||
"main_item_code",
|
||||
],
|
||||
filters={"docstatus": 1, "reference_name": ("in", list(pr_items)), "parenttype": doctype},
|
||||
)
|
||||
|
||||
def __set_alternative_item_details(self, row):
|
||||
if row.get("original_item"):
|
||||
self.alternative_item_details[row.get("original_item")] = row
|
||||
|
||||
def __get_pending_qty_to_receive(self):
|
||||
"""Get qty to be received against the purchase order."""
|
||||
|
||||
self.qty_to_be_received = defaultdict(float)
|
||||
|
||||
if (
|
||||
self.doctype != "Purchase Order" and self.backflush_based_on != "BOM" and self.purchase_orders
|
||||
):
|
||||
for row in frappe.get_all(
|
||||
"Purchase Order Item",
|
||||
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
|
||||
filters={"docstatus": 1, "parent": ("in", self.purchase_orders)},
|
||||
):
|
||||
|
||||
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
||||
|
||||
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
|
||||
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
|
||||
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
|
||||
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"name": "bom_detail_no",
|
||||
"source_warehouse": "reserve_warehouse",
|
||||
}
|
||||
for field in [
|
||||
"item_code",
|
||||
"name",
|
||||
"rate",
|
||||
"stock_uom",
|
||||
"source_warehouse",
|
||||
"description",
|
||||
"item_name",
|
||||
"stock_uom",
|
||||
]:
|
||||
fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
|
||||
|
||||
filters = [
|
||||
[doctype, "parent", "=", bom_no],
|
||||
[doctype, "docstatus", "=", 1],
|
||||
["BOM", "item", "=", item_code],
|
||||
[doctype, "sourced_by_supplier", "=", 0],
|
||||
]
|
||||
|
||||
return (
|
||||
frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
|
||||
)
|
||||
|
||||
def __remove_changed_rows(self):
|
||||
if not self.__changed_name:
|
||||
return
|
||||
|
||||
i = 1
|
||||
self.set(self.raw_material_table, [])
|
||||
for d in self._doc_before_save.supplied_items:
|
||||
if d.reference_name in self.__changed_name:
|
||||
continue
|
||||
|
||||
if d.reference_name not in self.__reference_name:
|
||||
continue
|
||||
|
||||
d.idx = i
|
||||
self.append("supplied_items", d)
|
||||
|
||||
i += 1
|
||||
|
||||
def __set_supplied_items(self):
|
||||
self.bom_items = {}
|
||||
|
||||
has_supplied_items = True if self.get(self.raw_material_table) else False
|
||||
for row in self.items:
|
||||
if self.doctype != "Purchase Order" and (
|
||||
(self.__changed_name and row.name not in self.__changed_name)
|
||||
or (has_supplied_items and not self.__changed_name)
|
||||
):
|
||||
continue
|
||||
|
||||
if self.doctype == "Purchase Order" or self.backflush_based_on == "BOM":
|
||||
for bom_item in self.__get_materials_from_bom(
|
||||
row.item_code, row.bom, row.get("include_exploded_items")
|
||||
):
|
||||
qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor
|
||||
bom_item.main_item_code = row.item_code
|
||||
self.__update_reserve_warehouse(bom_item, row)
|
||||
self.__set_alternative_item(bom_item)
|
||||
self.__add_supplied_item(row, bom_item, qty)
|
||||
|
||||
elif self.backflush_based_on != "BOM":
|
||||
for key, transfer_item in self.available_materials.items():
|
||||
if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0:
|
||||
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
|
||||
transfer_item.qty -= qty
|
||||
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
||||
|
||||
if self.qty_to_be_received:
|
||||
self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty
|
||||
|
||||
def __update_reserve_warehouse(self, row, item):
|
||||
if self.doctype == "Purchase Order":
|
||||
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
|
||||
|
||||
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
||||
key = (item_row.item_code, item_row.purchase_order)
|
||||
|
||||
if self.qty_to_be_received == item_row.qty:
|
||||
return transfer_item.qty
|
||||
|
||||
if self.qty_to_be_received:
|
||||
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
|
||||
transfer_item.item_details.required_qty = transfer_item.qty
|
||||
|
||||
if transfer_item.serial_no or frappe.get_cached_value(
|
||||
"UOM", transfer_item.item_details.stock_uom, "must_be_whole_number"
|
||||
):
|
||||
return frappe.utils.ceil(qty)
|
||||
|
||||
return qty
|
||||
|
||||
def __set_alternative_item(self, bom_item):
|
||||
if self.alternative_item_details.get(bom_item.rm_item_code):
|
||||
bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
|
||||
|
||||
def __add_supplied_item(self, item_row, bom_item, qty):
|
||||
bom_item.conversion_factor = item_row.conversion_factor
|
||||
rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
rm_obj.reference_name = item_row.name
|
||||
|
||||
if self.doctype == "Purchase Order":
|
||||
rm_obj.required_qty = qty
|
||||
else:
|
||||
rm_obj.consumed_qty = 0
|
||||
rm_obj.purchase_order = item_row.purchase_order
|
||||
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
|
||||
|
||||
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
|
||||
|
||||
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||
new_rm_obj = None
|
||||
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
||||
if batch_qty >= qty:
|
||||
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
||||
self.available_materials[key]["batch_no"][batch_no] -= qty
|
||||
return
|
||||
|
||||
elif qty > 0 and batch_qty > 0:
|
||||
qty -= batch_qty
|
||||
new_rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
new_rm_obj.reference_name = item_row.name
|
||||
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
|
||||
self.available_materials[key]["batch_no"][batch_no] = 0
|
||||
|
||||
if abs(qty) > 0 and not new_rm_obj:
|
||||
self.__set_consumed_qty(rm_obj, qty)
|
||||
else:
|
||||
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
|
||||
rm_obj.required_qty = required_qty
|
||||
rm_obj.consumed_qty = consumed_qty
|
||||
|
||||
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
|
||||
rm_obj.update(
|
||||
{
|
||||
"consumed_qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"required_qty": qty,
|
||||
"purchase_order": item_row.purchase_order,
|
||||
}
|
||||
)
|
||||
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __set_serial_nos(self, item_row, rm_obj):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
|
||||
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
||||
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = "\n".join(used_serial_nos)
|
||||
|
||||
# Removed the used serial nos from the list
|
||||
for sn in used_serial_nos:
|
||||
self.available_materials[key]["serial_no"].remove(sn)
|
||||
|
||||
def set_consumed_qty_in_po(self):
|
||||
# Update consumed qty back in the purchase order
|
||||
if not self.is_subcontracted:
|
||||
return
|
||||
|
||||
self.__get_purchase_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
|
||||
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
|
||||
self.__update_consumed_qty_in_po(itemwise_consumed_qty)
|
||||
|
||||
def __update_consumed_qty_in_po(self, itemwise_consumed_qty):
|
||||
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
|
||||
filters = {"docstatus": 1, "parent": ("in", self.purchase_orders)}
|
||||
|
||||
for row in frappe.get_all(
|
||||
"Purchase Order Item Supplied", fields=fields, filters=filters, order_by="idx"
|
||||
):
|
||||
key = (row.rm_item_code, row.main_item_code, row.parent)
|
||||
consumed_qty = itemwise_consumed_qty.get(key, 0)
|
||||
|
||||
if row.supplied_qty < consumed_qty:
|
||||
consumed_qty = row.supplied_qty
|
||||
|
||||
itemwise_consumed_qty[key] -= consumed_qty
|
||||
frappe.db.set_value("Purchase Order Item Supplied", row.name, "consumed_qty", consumed_qty)
|
||||
|
||||
def __validate_supplied_items(self):
|
||||
if self.doctype not in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
return
|
||||
|
||||
for row in self.get(self.raw_material_table):
|
||||
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
|
||||
if not self.__transferred_items or not self.__transferred_items.get(key):
|
||||
return
|
||||
|
||||
self.__validate_batch_no(row, key)
|
||||
self.__validate_serial_no(row, key)
|
||||
|
||||
def __validate_batch_no(self, row, key):
|
||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||
"batch_no"
|
||||
):
|
||||
link = get_link_to_form("Purchase Order", row.purchase_order)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
|
||||
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
if row.get("serial_no"):
|
||||
serial_nos = get_serial_nos(row.get("serial_no"))
|
||||
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no"))
|
||||
|
||||
if incorrect_sn:
|
||||
incorrect_sn = "\n".join(incorrect_sn)
|
||||
link = get_link_to_form("Purchase Order", row.purchase_order)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}"
|
||||
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
|
902
erpnext/controllers/subcontracting_controller.py
Normal file
902
erpnext/controllers/subcontracting_controller.py
Normal file
@ -0,0 +1,902 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import copy
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr, flt, get_link_to_form
|
||||
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
class SubcontractingController(StockController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SubcontractingController, self).__init__(*args, **kwargs)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Purchase Order",
|
||||
"order_field": "purchase_order",
|
||||
"rm_detail_field": "po_detail",
|
||||
"receipt_supplied_items_field": "Purchase Receipt Item Supplied",
|
||||
"order_supplied_items_field": "Purchase Order Item Supplied",
|
||||
}
|
||||
)
|
||||
else:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Subcontracting Order",
|
||||
"order_field": "subcontracting_order",
|
||||
"rm_detail_field": "sco_rm_detail",
|
||||
"receipt_supplied_items_field": "Subcontracting Receipt Supplied Item",
|
||||
"order_supplied_items_field": "Subcontracting Order Supplied Item",
|
||||
}
|
||||
)
|
||||
|
||||
def before_validate(self):
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.remove_empty_rows()
|
||||
self.set_items_conversion_factor()
|
||||
|
||||
def validate(self):
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.validate_items()
|
||||
self.create_raw_materials_supplied()
|
||||
else:
|
||||
super(SubcontractingController, self).validate()
|
||||
|
||||
def remove_empty_rows(self):
|
||||
for key in ["service_items", "items", "supplied_items"]:
|
||||
if self.get(key):
|
||||
idx = 1
|
||||
for item in self.get(key)[:]:
|
||||
if not (item.get("item_code") or item.get("main_item_code")):
|
||||
self.get(key).remove(item)
|
||||
else:
|
||||
item.idx = idx
|
||||
idx += 1
|
||||
|
||||
def set_items_conversion_factor(self):
|
||||
for item in self.get("items"):
|
||||
if not item.conversion_factor:
|
||||
item.conversion_factor = 1
|
||||
|
||||
def validate_items(self):
|
||||
for item in self.items:
|
||||
if not frappe.get_value("Item", item.item_code, "is_sub_contracted_item"):
|
||||
msg = f"Item {item.item_name} must be a subcontracted item."
|
||||
frappe.throw(_(msg))
|
||||
if item.bom:
|
||||
bom = frappe.get_doc("BOM", item.bom)
|
||||
if not bom.is_active:
|
||||
msg = f"Please select an active BOM for Item {item.item_name}."
|
||||
frappe.throw(_(msg))
|
||||
if bom.item != item.item_code:
|
||||
msg = f"Please select an valid BOM for Item {item.item_name}."
|
||||
frappe.throw(_(msg))
|
||||
|
||||
def __get_data_before_save(self):
|
||||
item_dict = {}
|
||||
if (
|
||||
self.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]
|
||||
and self._doc_before_save
|
||||
):
|
||||
for row in self._doc_before_save.get("items"):
|
||||
item_dict[row.name] = (row.item_code, row.qty)
|
||||
|
||||
return item_dict
|
||||
|
||||
def __identify_change_in_item_table(self):
|
||||
self.__changed_name = []
|
||||
self.__reference_name = []
|
||||
|
||||
if self.doctype in ["Purchase Order", "Subcontracting Order"] or self.is_new():
|
||||
self.set(self.raw_material_table, [])
|
||||
return
|
||||
|
||||
item_dict = self.__get_data_before_save()
|
||||
if not item_dict:
|
||||
return True
|
||||
|
||||
for row in self.items:
|
||||
self.__reference_name.append(row.name)
|
||||
if (row.name not in item_dict) or (row.item_code, row.qty) != item_dict[row.name]:
|
||||
self.__changed_name.append(row.name)
|
||||
|
||||
if item_dict.get(row.name):
|
||||
del item_dict[row.name]
|
||||
|
||||
self.__changed_name.extend(item_dict.keys())
|
||||
|
||||
def __get_backflush_based_on(self):
|
||||
self.backflush_based_on = frappe.db.get_single_value(
|
||||
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
|
||||
)
|
||||
|
||||
def initialized_fields(self):
|
||||
self.available_materials = frappe._dict()
|
||||
self.__transferred_items = frappe._dict()
|
||||
self.alternative_item_details = frappe._dict()
|
||||
self.__get_backflush_based_on()
|
||||
|
||||
def __get_subcontract_orders(self):
|
||||
self.subcontract_orders = []
|
||||
|
||||
if self.doctype in ["Purchase Order", "Subcontracting Order"]:
|
||||
return
|
||||
|
||||
self.subcontract_orders = [
|
||||
item.get(self.subcontract_data.order_field)
|
||||
for item in self.items
|
||||
if item.get(self.subcontract_data.order_field)
|
||||
]
|
||||
|
||||
def __get_pending_qty_to_receive(self):
|
||||
"""Get qty to be received against the subcontract order."""
|
||||
|
||||
self.qty_to_be_received = defaultdict(float)
|
||||
|
||||
if (
|
||||
self.doctype != self.subcontract_data.order_doctype
|
||||
and self.backflush_based_on != "BOM"
|
||||
and self.subcontract_orders
|
||||
):
|
||||
for row in frappe.get_all(
|
||||
f"{self.subcontract_data.order_doctype} Item",
|
||||
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
|
||||
filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
|
||||
):
|
||||
|
||||
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
||||
|
||||
def __get_transferred_items(self):
|
||||
fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"]
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"subcontracted_item": "main_item_code",
|
||||
"basic_rate": "rate",
|
||||
}
|
||||
|
||||
child_table_fields = [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"description",
|
||||
"qty",
|
||||
"basic_rate",
|
||||
"amount",
|
||||
"serial_no",
|
||||
"uom",
|
||||
"subcontracted_item",
|
||||
"stock_uom",
|
||||
"batch_no",
|
||||
"conversion_factor",
|
||||
"s_warehouse",
|
||||
"t_warehouse",
|
||||
"item_group",
|
||||
self.subcontract_data.rm_detail_field,
|
||||
]
|
||||
|
||||
if self.backflush_based_on == "BOM":
|
||||
child_table_fields.append("original_item")
|
||||
|
||||
for field in child_table_fields:
|
||||
fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}")
|
||||
|
||||
filters = [
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["Stock Entry", "purpose", "=", "Send to Subcontractor"],
|
||||
["Stock Entry", self.subcontract_data.order_field, "in", self.subcontract_orders],
|
||||
]
|
||||
|
||||
return frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||
|
||||
def __set_alternative_item_details(self, row):
|
||||
if row.get("original_item"):
|
||||
self.alternative_item_details[row.get("original_item")] = row
|
||||
|
||||
def __get_received_items(self, doctype):
|
||||
fields = []
|
||||
for field in ["name", self.subcontract_data.order_field, "parent"]:
|
||||
fields.append(f"`tab{doctype} Item`.`{field}`")
|
||||
|
||||
filters = [
|
||||
[doctype, "docstatus", "=", 1],
|
||||
[f"{doctype} Item", self.subcontract_data.order_field, "in", self.subcontract_orders],
|
||||
]
|
||||
if doctype == "Purchase Invoice":
|
||||
filters.append(["Purchase Invoice", "update_stock", "=", 1])
|
||||
|
||||
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
||||
|
||||
def __get_consumed_items(self, doctype, receipt_items):
|
||||
return frappe.get_all(
|
||||
self.subcontract_data.receipt_supplied_items_field,
|
||||
fields=[
|
||||
"serial_no",
|
||||
"rm_item_code",
|
||||
"reference_name",
|
||||
"batch_no",
|
||||
"consumed_qty",
|
||||
"main_item_code",
|
||||
],
|
||||
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
|
||||
)
|
||||
|
||||
def __update_consumed_materials(self, doctype, return_consumed_items=False):
|
||||
"""Deduct the consumed materials from the available materials."""
|
||||
|
||||
receipt_items = self.__get_received_items(doctype)
|
||||
if not receipt_items:
|
||||
return ([], {}) if return_consumed_items else None
|
||||
|
||||
receipt_items = {
|
||||
item.name: item.get(self.subcontract_data.order_field) for item in receipt_items
|
||||
}
|
||||
consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys())
|
||||
|
||||
if return_consumed_items:
|
||||
return (consumed_materials, receipt_items)
|
||||
|
||||
for row in consumed_materials:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
if not self.available_materials.get(key):
|
||||
continue
|
||||
|
||||
self.available_materials[key]["qty"] -= row.consumed_qty
|
||||
if row.serial_no:
|
||||
self.available_materials[key]["serial_no"] = list(
|
||||
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
|
||||
)
|
||||
|
||||
if row.batch_no:
|
||||
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
|
||||
|
||||
def get_available_materials(self):
|
||||
"""Get the available raw materials which has been transferred to the supplier.
|
||||
available_materials = {
|
||||
(item_code, subcontracted_item, subcontract_order): {
|
||||
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
|
||||
}
|
||||
}
|
||||
"""
|
||||
if not self.subcontract_orders:
|
||||
return
|
||||
|
||||
for row in self.__get_transferred_items():
|
||||
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
||||
|
||||
if key not in self.available_materials:
|
||||
self.available_materials.setdefault(
|
||||
key,
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": 0,
|
||||
"serial_no": [],
|
||||
"batch_no": defaultdict(float),
|
||||
"item_details": row,
|
||||
f"{self.subcontract_data.rm_detail_field}s": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
details = self.available_materials[key]
|
||||
details.qty += row.qty
|
||||
details[f"{self.subcontract_data.rm_detail_field}s"].append(
|
||||
row.get(self.subcontract_data.rm_detail_field)
|
||||
)
|
||||
|
||||
if row.serial_no:
|
||||
details.serial_no.extend(get_serial_nos(row.serial_no))
|
||||
|
||||
if row.batch_no:
|
||||
details.batch_no[row.batch_no] += row.qty
|
||||
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
self.__update_consumed_materials(doctype)
|
||||
else:
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
|
||||
def __remove_changed_rows(self):
|
||||
if not self.__changed_name:
|
||||
return
|
||||
|
||||
i = 1
|
||||
self.set(self.raw_material_table, [])
|
||||
for item in self._doc_before_save.supplied_items:
|
||||
if item.reference_name in self.__changed_name:
|
||||
continue
|
||||
|
||||
if item.reference_name not in self.__reference_name:
|
||||
continue
|
||||
|
||||
item.idx = i
|
||||
self.append("supplied_items", item)
|
||||
|
||||
i += 1
|
||||
|
||||
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
|
||||
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
|
||||
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
|
||||
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"name": "bom_detail_no",
|
||||
"source_warehouse": "reserve_warehouse",
|
||||
}
|
||||
for field in [
|
||||
"item_code",
|
||||
"name",
|
||||
"rate",
|
||||
"stock_uom",
|
||||
"source_warehouse",
|
||||
"description",
|
||||
"item_name",
|
||||
"stock_uom",
|
||||
]:
|
||||
fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
|
||||
|
||||
filters = [
|
||||
[doctype, "parent", "=", bom_no],
|
||||
[doctype, "docstatus", "=", 1],
|
||||
["BOM", "item", "=", item_code],
|
||||
[doctype, "sourced_by_supplier", "=", 0],
|
||||
]
|
||||
|
||||
return (
|
||||
frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
|
||||
)
|
||||
|
||||
def __update_reserve_warehouse(self, row, item):
|
||||
if self.doctype == self.subcontract_data.order_doctype:
|
||||
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
|
||||
|
||||
def __set_alternative_item(self, bom_item):
|
||||
if self.alternative_item_details.get(bom_item.rm_item_code):
|
||||
bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
|
||||
|
||||
def __set_serial_nos(self, item_row, rm_obj):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
||||
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = "\n".join(used_serial_nos)
|
||||
|
||||
# Removed the used serial nos from the list
|
||||
for sn in used_serial_nos:
|
||||
self.available_materials[key]["serial_no"].remove(sn)
|
||||
|
||||
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
|
||||
rm_obj.update(
|
||||
{
|
||||
"consumed_qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"required_qty": qty,
|
||||
self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field),
|
||||
}
|
||||
)
|
||||
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
|
||||
rm_obj.required_qty = required_qty
|
||||
rm_obj.consumed_qty = consumed_qty
|
||||
|
||||
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
|
||||
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||
new_rm_obj = None
|
||||
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
||||
if batch_qty >= qty:
|
||||
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
||||
self.available_materials[key]["batch_no"][batch_no] -= qty
|
||||
return
|
||||
|
||||
elif qty > 0 and batch_qty > 0:
|
||||
qty -= batch_qty
|
||||
new_rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
new_rm_obj.reference_name = item_row.name
|
||||
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
|
||||
self.available_materials[key]["batch_no"][batch_no] = 0
|
||||
|
||||
if abs(qty) > 0 and not new_rm_obj:
|
||||
self.__set_consumed_qty(rm_obj, qty)
|
||||
else:
|
||||
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __add_supplied_item(self, item_row, bom_item, qty):
|
||||
bom_item.conversion_factor = item_row.conversion_factor
|
||||
rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
rm_obj.reference_name = item_row.name
|
||||
|
||||
if self.doctype == "Subcontracting Receipt":
|
||||
args = frappe._dict(
|
||||
{
|
||||
"item_code": rm_obj.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * flt(rm_obj.consumed_qty),
|
||||
"serial_no": rm_obj.serial_no,
|
||||
"batch_no": rm_obj.batch_no,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"company": self.company,
|
||||
"allow_zero_valuation": 1,
|
||||
}
|
||||
)
|
||||
rm_obj.rate = get_incoming_rate(args)
|
||||
|
||||
if self.doctype == self.subcontract_data.order_doctype:
|
||||
rm_obj.required_qty = qty
|
||||
rm_obj.amount = rm_obj.required_qty * rm_obj.rate
|
||||
else:
|
||||
rm_obj.consumed_qty = 0
|
||||
setattr(
|
||||
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
|
||||
|
||||
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
||||
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
|
||||
if self.qty_to_be_received == item_row.qty:
|
||||
return transfer_item.qty
|
||||
|
||||
if self.qty_to_be_received:
|
||||
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
|
||||
transfer_item.item_details.required_qty = transfer_item.qty
|
||||
|
||||
if transfer_item.serial_no or frappe.get_cached_value(
|
||||
"UOM", transfer_item.item_details.stock_uom, "must_be_whole_number"
|
||||
):
|
||||
return frappe.utils.ceil(qty)
|
||||
|
||||
return qty
|
||||
|
||||
def __set_supplied_items(self):
|
||||
self.bom_items = {}
|
||||
|
||||
has_supplied_items = True if self.get(self.raw_material_table) else False
|
||||
for row in self.items:
|
||||
if self.doctype != self.subcontract_data.order_doctype and (
|
||||
(self.__changed_name and row.name not in self.__changed_name)
|
||||
or (has_supplied_items and not self.__changed_name)
|
||||
):
|
||||
continue
|
||||
|
||||
if self.doctype == self.subcontract_data.order_doctype or self.backflush_based_on == "BOM":
|
||||
for bom_item in self.__get_materials_from_bom(
|
||||
row.item_code, row.bom, row.get("include_exploded_items")
|
||||
):
|
||||
qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor
|
||||
bom_item.main_item_code = row.item_code
|
||||
self.__update_reserve_warehouse(bom_item, row)
|
||||
self.__set_alternative_item(bom_item)
|
||||
self.__add_supplied_item(row, bom_item, qty)
|
||||
|
||||
elif self.backflush_based_on != "BOM":
|
||||
for key, transfer_item in self.available_materials.items():
|
||||
if (key[1], key[2]) == (
|
||||
row.item_code,
|
||||
row.get(self.subcontract_data.order_field),
|
||||
) and transfer_item.qty > 0:
|
||||
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
|
||||
transfer_item.qty -= qty
|
||||
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
||||
|
||||
if self.qty_to_be_received:
|
||||
self.qty_to_be_received[
|
||||
(row.item_code, row.get(self.subcontract_data.order_field))
|
||||
] -= row.qty
|
||||
|
||||
def __prepare_supplied_items(self):
|
||||
self.initialized_fields()
|
||||
self.__get_subcontract_orders()
|
||||
self.__get_pending_qty_to_receive()
|
||||
self.get_available_materials()
|
||||
self.__remove_changed_rows()
|
||||
self.__set_supplied_items()
|
||||
|
||||
def __validate_batch_no(self, row, key):
|
||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||
"batch_no"
|
||||
):
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the {self.subcontract_data.order_doctype} {link}'
|
||||
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
if row.get("serial_no"):
|
||||
serial_nos = get_serial_nos(row.get("serial_no"))
|
||||
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no"))
|
||||
|
||||
if incorrect_sn:
|
||||
incorrect_sn = "\n".join(incorrect_sn)
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the {self.subcontract_data.order_doctype} {link}"
|
||||
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
|
||||
|
||||
def __validate_supplied_items(self):
|
||||
if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
return
|
||||
|
||||
for row in self.get(self.raw_material_table):
|
||||
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
||||
if not self.__transferred_items or not self.__transferred_items.get(key):
|
||||
return
|
||||
|
||||
self.__validate_batch_no(row, key)
|
||||
self.__validate_serial_no(row, key)
|
||||
|
||||
def set_materials_for_subcontracted_items(self, raw_material_table):
|
||||
if self.doctype == "Purchase Invoice" and not self.update_stock:
|
||||
return
|
||||
|
||||
self.raw_material_table = raw_material_table
|
||||
self.__identify_change_in_item_table()
|
||||
self.__prepare_supplied_items()
|
||||
self.__validate_supplied_items()
|
||||
|
||||
def create_raw_materials_supplied(self, raw_material_table="supplied_items"):
|
||||
self.set_materials_for_subcontracted_items(raw_material_table)
|
||||
|
||||
if self.doctype in ["Subcontracting Receipt", "Purchase Receipt", "Purchase Invoice"]:
|
||||
for item in self.get("items"):
|
||||
item.rm_supp_cost = 0.0
|
||||
|
||||
def __update_consumed_qty_in_subcontract_order(self, itemwise_consumed_qty):
|
||||
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
|
||||
filters = {"docstatus": 1, "parent": ("in", self.subcontract_orders)}
|
||||
|
||||
for row in frappe.get_all(
|
||||
self.subcontract_data.order_supplied_items_field, fields=fields, filters=filters, order_by="idx"
|
||||
):
|
||||
key = (row.rm_item_code, row.main_item_code, row.parent)
|
||||
consumed_qty = itemwise_consumed_qty.get(key, 0)
|
||||
|
||||
if row.supplied_qty < consumed_qty:
|
||||
consumed_qty = row.supplied_qty
|
||||
|
||||
itemwise_consumed_qty[key] -= consumed_qty
|
||||
frappe.db.set_value(
|
||||
self.subcontract_data.order_supplied_items_field, row.name, "consumed_qty", consumed_qty
|
||||
)
|
||||
|
||||
def set_consumed_qty_in_subcontract_order(self):
|
||||
# Update consumed qty back in the subcontract order
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
|
||||
"is_old_subcontracting_flow"
|
||||
):
|
||||
self.__get_subcontract_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
doctypes = ["Purchase Receipt", "Purchase Invoice"]
|
||||
else:
|
||||
doctypes = ["Subcontracting Receipt"]
|
||||
|
||||
for doctype in doctypes:
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
doctype, return_consumed_items=True
|
||||
)
|
||||
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
|
||||
self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty)
|
||||
|
||||
def update_ordered_and_reserved_qty(self):
|
||||
sco_map = {}
|
||||
for item in self.get("items"):
|
||||
if self.doctype == "Subcontracting Receipt" and item.subcontracting_order:
|
||||
sco_map.setdefault(item.subcontracting_order, []).append(item.subcontracting_order_item)
|
||||
|
||||
for sco, sco_item_rows in sco_map.items():
|
||||
if sco and sco_item_rows:
|
||||
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
||||
|
||||
if sco_doc.status in ["Closed", "Cancelled"]:
|
||||
frappe.throw(
|
||||
_("{0} {1} is cancelled or closed").format(_("Subcontracting Order"), sco),
|
||||
frappe.InvalidStatusError,
|
||||
)
|
||||
|
||||
sco_doc.update_ordered_qty_for_subcontracting(sco_item_rows)
|
||||
sco_doc.update_reserved_qty_for_subcontracting()
|
||||
|
||||
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
|
||||
if hasattr(self, "supplied_items"):
|
||||
for item in self.get("supplied_items"):
|
||||
# negative quantity is passed, as raw material qty has to be decreased
|
||||
# when SCR is submitted and it has to be increased when SCR is cancelled
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
item,
|
||||
{
|
||||
"item_code": item.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"actual_qty": -1 * flt(item.consumed_qty),
|
||||
"dependant_sle_voucher_detail_no": item.reference_name,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||
self.update_ordered_and_reserved_qty()
|
||||
|
||||
sl_entries = []
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.item_code in stock_items and item.warehouse:
|
||||
scr_qty = flt(item.qty) * flt(item.conversion_factor)
|
||||
|
||||
if scr_qty:
|
||||
sle = self.get_sl_entries(
|
||||
item, {"actual_qty": flt(scr_qty), "serial_no": cstr(item.serial_no).strip()}
|
||||
)
|
||||
rate_db_precision = 6 if cint(self.precision("rate", item)) <= 6 else 9
|
||||
incoming_rate = flt(item.rate, rate_db_precision)
|
||||
sle.update(
|
||||
{
|
||||
"incoming_rate": incoming_rate,
|
||||
"recalculate_rate": 1,
|
||||
}
|
||||
)
|
||||
sl_entries.append(sle)
|
||||
|
||||
if flt(item.rejected_qty) != 0:
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
item,
|
||||
{
|
||||
"warehouse": item.rejected_warehouse,
|
||||
"actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor),
|
||||
"serial_no": cstr(item.rejected_serial_no).strip(),
|
||||
"incoming_rate": 0.0,
|
||||
"recalculate_rate": 1,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
for item in self.get("supplied_items"):
|
||||
if item.reference_name == item_row_id:
|
||||
if (
|
||||
self.get("is_old_subcontracting_flow")
|
||||
and reset_outgoing_rate
|
||||
and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item")
|
||||
):
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": item.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * item.consumed_qty,
|
||||
"serial_no": item.serial_no,
|
||||
"batch_no": item.batch_no,
|
||||
}
|
||||
)
|
||||
|
||||
if rate > 0:
|
||||
item.rate = rate
|
||||
|
||||
item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount"))
|
||||
supplied_items_cost += item.amount
|
||||
|
||||
return supplied_items_cost
|
||||
|
||||
def set_subcontracting_order_status(self):
|
||||
if self.doctype == "Subcontracting Order":
|
||||
self.update_status()
|
||||
elif self.doctype == "Subcontracting Receipt":
|
||||
self.__get_subcontract_orders
|
||||
|
||||
if self.subcontract_orders:
|
||||
for sco in set(self.subcontract_orders):
|
||||
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
||||
sco_doc.update_status()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_current_stock(self):
|
||||
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||
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
|
||||
|
||||
@property
|
||||
def sub_contracted_items(self):
|
||||
if not hasattr(self, "_sub_contracted_items"):
|
||||
self._sub_contracted_items = []
|
||||
item_codes = list(set(item.item_code for item in self.get("items")))
|
||||
if item_codes:
|
||||
items = frappe.get_all(
|
||||
"Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1}
|
||||
)
|
||||
self._sub_contracted_items = [item.name for item in items]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
|
||||
def get_item_details(items):
|
||||
item = frappe.qb.DocType("Item")
|
||||
item_list = (
|
||||
frappe.qb.from_(item)
|
||||
.select(item.item_code, item.description, item.allow_alternative_item)
|
||||
.where(item.name.isin(items))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
item_details = {}
|
||||
for item in item_list:
|
||||
item_details[item.item_code] = item
|
||||
|
||||
return item_details
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_rm_stock_entry(subcontract_order, rm_items, order_doctype="Subcontracting Order"):
|
||||
rm_items_list = rm_items
|
||||
|
||||
if isinstance(rm_items, str):
|
||||
rm_items_list = json.loads(rm_items)
|
||||
elif not rm_items:
|
||||
frappe.throw(_("No Items available for transfer"))
|
||||
|
||||
if rm_items_list:
|
||||
fg_items = list(set(item["item_code"] for item in rm_items_list))
|
||||
else:
|
||||
frappe.throw(_("No Items selected for transfer"))
|
||||
|
||||
if subcontract_order:
|
||||
subcontract_order = frappe.get_doc(order_doctype, subcontract_order)
|
||||
|
||||
if fg_items:
|
||||
items = tuple(set(item["rm_item_code"] for item in rm_items_list))
|
||||
item_wh = get_item_details(items)
|
||||
|
||||
stock_entry = frappe.new_doc("Stock Entry")
|
||||
stock_entry.purpose = "Send to Subcontractor"
|
||||
if order_doctype == "Purchase Order":
|
||||
stock_entry.purchase_order = subcontract_order.name
|
||||
else:
|
||||
stock_entry.subcontracting_order = subcontract_order.name
|
||||
stock_entry.supplier = subcontract_order.supplier
|
||||
stock_entry.supplier_name = subcontract_order.supplier_name
|
||||
stock_entry.supplier_address = subcontract_order.supplier_address
|
||||
stock_entry.address_display = subcontract_order.address_display
|
||||
stock_entry.company = subcontract_order.company
|
||||
stock_entry.to_warehouse = subcontract_order.supplier_warehouse
|
||||
stock_entry.set_stock_entry_type()
|
||||
|
||||
if order_doctype == "Purchase Order":
|
||||
rm_detail_field = "po_detail"
|
||||
else:
|
||||
rm_detail_field = "sco_rm_detail"
|
||||
|
||||
for item_code in fg_items:
|
||||
for rm_item_data in rm_items_list:
|
||||
if rm_item_data["item_code"] == item_code:
|
||||
rm_item_code = rm_item_data["rm_item_code"]
|
||||
items_dict = {
|
||||
rm_item_code: {
|
||||
rm_detail_field: rm_item_data.get("name"),
|
||||
"item_name": rm_item_data["item_name"],
|
||||
"description": item_wh.get(rm_item_code, {}).get("description", ""),
|
||||
"qty": rm_item_data["qty"],
|
||||
"from_warehouse": rm_item_data["warehouse"],
|
||||
"stock_uom": rm_item_data["stock_uom"],
|
||||
"serial_no": rm_item_data.get("serial_no"),
|
||||
"batch_no": rm_item_data.get("batch_no"),
|
||||
"main_item_code": rm_item_data["item_code"],
|
||||
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
|
||||
}
|
||||
}
|
||||
stock_entry.add_to_stock_entry_detail(items_dict)
|
||||
return stock_entry.as_dict()
|
||||
else:
|
||||
frappe.throw(_("No Items selected for transfer"))
|
||||
return subcontract_order.name
|
||||
|
||||
|
||||
def add_items_in_ste(
|
||||
ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_detail", batch_no=None
|
||||
):
|
||||
item = ste_doc.append("items", row.item_details)
|
||||
|
||||
rm_detail = list(set(row.get(f"{rm_detail_field}s")).intersection(rm_details))
|
||||
item.update(
|
||||
{
|
||||
"qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"basic_rate": row.item_details["rate"],
|
||||
rm_detail_field: rm_detail[0] if rm_detail else "",
|
||||
"s_warehouse": row.item_details["t_warehouse"],
|
||||
"t_warehouse": row.item_details["s_warehouse"],
|
||||
"item_code": row.item_details["rm_item_code"],
|
||||
"subcontracted_item": row.item_details["main_item_code"],
|
||||
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def make_return_stock_entry_for_subcontract(
|
||||
available_materials, order_doc, rm_details, order_doctype="Subcontracting Order"
|
||||
):
|
||||
ste_doc = frappe.new_doc("Stock Entry")
|
||||
ste_doc.purpose = "Material Transfer"
|
||||
|
||||
if order_doctype == "Purchase Order":
|
||||
ste_doc.purchase_order = order_doc.name
|
||||
rm_detail_field = "po_detail"
|
||||
else:
|
||||
ste_doc.subcontracting_order = order_doc.name
|
||||
rm_detail_field = "sco_rm_detail"
|
||||
ste_doc.company = order_doc.company
|
||||
ste_doc.is_return = 1
|
||||
|
||||
for key, value in available_materials.items():
|
||||
if not value.qty:
|
||||
continue
|
||||
|
||||
if value.batch_no:
|
||||
for batch_no, qty in value.batch_no.items():
|
||||
if qty > 0:
|
||||
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no)
|
||||
else:
|
||||
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field)
|
||||
|
||||
ste_doc.set_stock_entry_type()
|
||||
ste_doc.calculate_rate_and_amount()
|
||||
|
||||
return ste_doc
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_materials_from_supplier(
|
||||
subcontract_order, rm_details, order_doctype="Subcontracting Order"
|
||||
):
|
||||
if isinstance(rm_details, str):
|
||||
rm_details = json.loads(rm_details)
|
||||
|
||||
doc = frappe.get_cached_doc(order_doctype, subcontract_order)
|
||||
doc.initialized_fields()
|
||||
doc.subcontract_orders = [doc.name]
|
||||
doc.get_available_materials()
|
||||
|
||||
if not doc.available_materials:
|
||||
frappe.throw(
|
||||
_("Materials are already received against the {0} {1}").format(order_doctype, subcontract_order)
|
||||
)
|
||||
|
||||
return make_return_stock_entry_for_subcontract(
|
||||
doc.available_materials, doc, rm_details, order_doctype
|
||||
)
|
1077
erpnext/controllers/tests/test_subcontracting_controller.py
Normal file
1077
erpnext/controllers/tests/test_subcontracting_controller.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -308,7 +308,6 @@ doc_events = {
|
||||
"Sales Taxes and Charges Template": {
|
||||
"on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings"
|
||||
},
|
||||
"Tax Category": {"validate": "erpnext.regional.india.utils.validate_tax_category"},
|
||||
"Sales Invoice": {
|
||||
"on_submit": [
|
||||
"erpnext.regional.create_transaction_log",
|
||||
@ -322,24 +321,15 @@ doc_events = {
|
||||
"erpnext.regional.saudi_arabia.utils.delete_qr_code_file",
|
||||
],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.validate_document_name",
|
||||
"erpnext.regional.india.utils.update_taxable_values",
|
||||
"erpnext.regional.india.utils.validate_sez_and_export_invoices",
|
||||
],
|
||||
},
|
||||
"POS Invoice": {"on_submit": ["erpnext.regional.saudi_arabia.utils.create_qr_code"]},
|
||||
"Purchase Invoice": {
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.validate_reverse_charge_transaction",
|
||||
"erpnext.regional.india.utils.update_itc_availed_fields",
|
||||
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
|
||||
"erpnext.regional.united_arab_emirates.utils.validate_returns",
|
||||
"erpnext.regional.india.utils.update_taxable_values",
|
||||
]
|
||||
},
|
||||
"Payment Entry": {
|
||||
"validate": "erpnext.regional.india.utils.update_place_of_supply",
|
||||
"on_submit": [
|
||||
"erpnext.regional.create_transaction_log",
|
||||
"erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status",
|
||||
@ -349,20 +339,9 @@ doc_events = {
|
||||
},
|
||||
"Address": {
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.validate_gstin_for_india",
|
||||
"erpnext.regional.italy.utils.set_state_code",
|
||||
"erpnext.regional.india.utils.update_gst_category",
|
||||
],
|
||||
},
|
||||
"Supplier": {"validate": "erpnext.regional.india.utils.validate_pan_for_india"},
|
||||
(
|
||||
"Sales Invoice",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Purchase Invoice",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
): {"validate": ["erpnext.regional.india.utils.set_place_of_supply"]},
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
|
||||
@ -374,12 +353,7 @@ doc_events = {
|
||||
("Quotation", "Sales Order", "Sales Invoice"): {
|
||||
"validate": ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"]
|
||||
},
|
||||
"Company": {
|
||||
"on_trash": [
|
||||
"erpnext.regional.india.utils.delete_gst_settings_for_company",
|
||||
"erpnext.regional.saudi_arabia.utils.delete_vat_settings_for_company",
|
||||
]
|
||||
},
|
||||
"Company": {"on_trash": ["erpnext.regional.saudi_arabia.utils.delete_vat_settings_for_company"]},
|
||||
"Integration Request": {
|
||||
"validate": "erpnext.accounts.doctype.payment_request.payment_request.validate_payment"
|
||||
},
|
||||
@ -546,16 +520,8 @@ regional_overrides = {
|
||||
"erpnext.tests.test_regional.test_method": "erpnext.regional.france.utils.test_method"
|
||||
},
|
||||
"India": {
|
||||
"erpnext.tests.test_regional.test_method": "erpnext.regional.india.utils.test_method",
|
||||
"erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header": "erpnext.regional.india.utils.get_itemised_tax_breakup_header",
|
||||
"erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data": "erpnext.regional.india.utils.get_itemised_tax_breakup_data",
|
||||
"erpnext.accounts.party.get_regional_address_details": "erpnext.regional.india.utils.get_regional_address_details",
|
||||
"erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts": "erpnext.regional.india.utils.get_regional_round_off_accounts",
|
||||
"erpnext.hr.utils.calculate_annual_eligible_hra_exemption": "erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption",
|
||||
"erpnext.hr.utils.calculate_hra_exemption_for_period": "erpnext.regional.india.utils.calculate_hra_exemption_for_period",
|
||||
"erpnext.controllers.accounts_controller.validate_einvoice_fields": "erpnext.regional.india.e_invoice.utils.validate_einvoice_fields",
|
||||
"erpnext.assets.doctype.asset.asset.get_depreciation_amount": "erpnext.regional.india.utils.get_depreciation_amount",
|
||||
"erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code": "erpnext.regional.india.utils.set_item_tax_from_hsn_code",
|
||||
},
|
||||
"United Arab Emirates": {
|
||||
"erpnext.controllers.taxes_and_totals.update_itemised_tax_data": "erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data",
|
||||
|
@ -9,7 +9,7 @@ import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import set_backflush_based_on
|
||||
from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom
|
||||
from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
|
||||
update_cost_in_all_boms_in_test,
|
||||
@ -18,7 +18,6 @@ from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
from erpnext.tests.test_subcontracting import set_backflush_based_on
|
||||
|
||||
test_records = frappe.get_test_records("BOM")
|
||||
test_dependencies = ["Item", "Quality Inspection Template"]
|
||||
@ -256,12 +255,29 @@ class TestBOM(FrappeTestCase):
|
||||
bom.submit()
|
||||
# test that sourced_by_supplier rate is zero even after updating cost
|
||||
self.assertEqual(bom.items[2].rate, 0)
|
||||
# test in Purchase Order sourced_by_supplier is not added to Supplied Item
|
||||
po = create_purchase_order(
|
||||
item_code=item_code, qty=1, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
get_subcontracting_order,
|
||||
make_service_item,
|
||||
)
|
||||
|
||||
make_service_item("Subcontracted Service Item 1")
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 1",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"fg_item": item_code,
|
||||
"fg_item_qty": 1,
|
||||
},
|
||||
]
|
||||
# test in Subcontracting Order sourced_by_supplier is not added to Supplied Item
|
||||
sco = get_subcontracting_order(
|
||||
service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||
)
|
||||
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
|
||||
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
||||
supplied_items = sorted([d.rm_item_code for d in sco.supplied_items])
|
||||
self.assertEqual(bom_items, supplied_items)
|
||||
|
||||
def test_bom_tree_representation(self):
|
||||
|
@ -508,7 +508,7 @@ class ProductionPlan(Document):
|
||||
po.is_subcontracted = 1
|
||||
for row in po_list:
|
||||
po_data = {
|
||||
"item_code": row.production_item,
|
||||
"fg_item": row.production_item,
|
||||
"warehouse": row.fg_warehouse,
|
||||
"production_plan_sub_assembly_item": row.name,
|
||||
"bom": row.bom_no,
|
||||
@ -518,9 +518,6 @@ class ProductionPlan(Document):
|
||||
for field in [
|
||||
"schedule_date",
|
||||
"qty",
|
||||
"uom",
|
||||
"stock_uom",
|
||||
"item_name",
|
||||
"description",
|
||||
"production_plan_item",
|
||||
]:
|
||||
|
@ -1243,7 +1243,7 @@ class TestWorkOrder(FrappeTestCase):
|
||||
ste_doc.insert()
|
||||
ste_doc.submit()
|
||||
|
||||
batch_list = [row.batch_no for row in ste_doc.items]
|
||||
batch_list = sorted([row.batch_no for row in ste_doc.items])
|
||||
|
||||
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
|
||||
transferred_ste_doc = frappe.get_doc(
|
||||
|
@ -36,7 +36,7 @@ def get_data(filters):
|
||||
"total_time_in_mins",
|
||||
]
|
||||
|
||||
for field in ["work_order", "workstation", "operation", "company"]:
|
||||
for field in ["work_order", "workstation", "operation", "status", "company"]:
|
||||
if filters.get(field):
|
||||
query_filters[field] = ("in", filters.get(field))
|
||||
|
||||
|
@ -21,3 +21,4 @@ Payroll
|
||||
Telephony
|
||||
Bulk Transaction
|
||||
E-commerce
|
||||
Subcontracting
|
@ -11,9 +11,7 @@ erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
|
||||
erpnext.patches.v5_7.update_item_description_based_on_item_master
|
||||
erpnext.patches.v4_2.repost_reserved_qty #2021-03-31
|
||||
execute:frappe.reload_doc("Payroll", "doctype", "salary_slip")
|
||||
erpnext.patches.v8_1.setup_gst_india #2017-06-27
|
||||
erpnext.patches.v8_1.removed_roles_from_gst_report_non_indian_account #16-08-2018
|
||||
erpnext.patches.v8_7.sync_india_custom_fields
|
||||
erpnext.patches.v10_0.fichier_des_ecritures_comptables_for_france
|
||||
erpnext.patches.v10_0.rename_price_to_rate_in_pricing_rule
|
||||
erpnext.patches.v10_0.set_currency_in_pricing_rule
|
||||
@ -47,7 +45,6 @@ erpnext.patches.v11_0.check_buying_selling_in_currency_exchange
|
||||
erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019
|
||||
erpnext.patches.v11_0.rename_overproduction_percent_field
|
||||
erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom
|
||||
erpnext.patches.v11_0.inter_state_field_for_gst
|
||||
erpnext.patches.v11_0.rename_members_with_naming_series #04-06-2018
|
||||
erpnext.patches.v11_0.set_update_field_and_value_in_workflow_state
|
||||
erpnext.patches.v11_0.update_total_qty_field
|
||||
@ -69,20 +66,15 @@ execute:frappe.delete_doc_if_exists("Page", "sales-analytics")
|
||||
execute:frappe.delete_doc_if_exists("Page", "purchase-analytics")
|
||||
execute:frappe.delete_doc_if_exists("Page", "stock-analytics")
|
||||
execute:frappe.delete_doc_if_exists("Page", "production-analytics")
|
||||
erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 #2019-04-01 #2019-04-26 #2019-05-03
|
||||
erpnext.patches.v11_0.drop_column_max_days_allowed
|
||||
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
|
||||
erpnext.patches.v11_0.update_delivery_trip_status
|
||||
erpnext.patches.v11_0.set_missing_gst_hsn_code
|
||||
erpnext.patches.v11_0.rename_bom_wo_fields
|
||||
erpnext.patches.v12_0.set_default_homepage_type
|
||||
erpnext.patches.v11_0.rename_additional_salary_component_additional_salary
|
||||
erpnext.patches.v11_0.renamed_from_to_fields_in_project
|
||||
erpnext.patches.v11_0.add_permissions_in_gst_settings #2020-04-04
|
||||
erpnext.patches.v11_1.setup_guardian_role
|
||||
execute:frappe.delete_doc('DocType', 'Notification Control')
|
||||
erpnext.patches.v12_0.set_gst_category
|
||||
erpnext.patches.v12_0.update_gst_category
|
||||
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
|
||||
erpnext.patches.v12_0.set_task_status
|
||||
erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
|
||||
@ -119,7 +111,6 @@ execute:frappe.delete_doc("DocType", "Project Task")
|
||||
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
|
||||
erpnext.patches.v12_0.update_due_date_in_gle
|
||||
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
|
||||
erpnext.patches.v12_0.update_ewaybill_field_position
|
||||
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes #2020-05-11
|
||||
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
|
||||
erpnext.patches.v12_0.move_plaid_settings_to_doctype
|
||||
@ -142,14 +133,12 @@ erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
|
||||
erpnext.patches.v12_0.set_automatically_process_deferred_accounting_in_accounts_settings
|
||||
erpnext.patches.v12_0.set_payment_entry_status
|
||||
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
|
||||
erpnext.patches.v12_0.add_export_type_field_in_party_master
|
||||
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
||||
erpnext.patches.v12_0.update_price_or_product_discount
|
||||
erpnext.patches.v12_0.set_production_capacity_in_workstation
|
||||
erpnext.patches.v12_0.set_employee_preferred_emails
|
||||
erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order
|
||||
erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim
|
||||
erpnext.patches.v12_0.add_eway_bill_in_delivery_note
|
||||
erpnext.patches.v12_0.set_lead_title_field
|
||||
erpnext.patches.v12_0.set_permission_einvoicing
|
||||
erpnext.patches.v12_0.set_job_offer_applicant_email
|
||||
@ -197,14 +186,12 @@ erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
|
||||
erpnext.patches.v13_0.update_subscription
|
||||
erpnext.patches.v12_0.unhide_cost_center_field
|
||||
erpnext.patches.v13_0.update_sla_enhancements
|
||||
erpnext.patches.v12_0.update_address_template_for_india
|
||||
erpnext.patches.v13_0.update_deferred_settings
|
||||
erpnext.patches.v12_0.set_multi_uom_in_rfq
|
||||
erpnext.patches.v13_0.delete_old_sales_reports
|
||||
execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
|
||||
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
|
||||
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
|
||||
erpnext.patches.v12_0.create_itc_reversal_custom_fields
|
||||
execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings")
|
||||
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
|
||||
erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
|
||||
@ -215,18 +202,15 @@ erpnext.patches.v12_0.update_item_tax_template_company
|
||||
erpnext.patches.v13_0.move_branch_code_to_bank_account
|
||||
erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24
|
||||
erpnext.patches.v13_0.stock_entry_enhancements
|
||||
erpnext.patches.v12_0.update_state_code_for_daman_and_diu
|
||||
erpnext.patches.v12_0.rename_lost_reason_detail
|
||||
erpnext.patches.v13_0.drop_razorpay_payload_column
|
||||
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
|
||||
erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports
|
||||
erpnext.patches.v13_0.rename_issue_doctype_fields
|
||||
erpnext.patches.v13_0.change_default_pos_print_format
|
||||
erpnext.patches.v13_0.set_youtube_video_id
|
||||
erpnext.patches.v13_0.set_app_name
|
||||
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
||||
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
||||
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||
@ -241,26 +225,19 @@ erpnext.patches.v13_0.set_company_in_leave_ledger_entry
|
||||
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
||||
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
|
||||
erpnext.patches.v13_0.update_payment_terms_outstanding
|
||||
erpnext.patches.v12_0.add_state_code_for_ladakh
|
||||
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
|
||||
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
|
||||
erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17
|
||||
erpnext.patches.v12_0.add_einvoice_summary_report_permissions
|
||||
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
|
||||
erpnext.patches.v13_0.setup_uae_vat_fields
|
||||
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
|
||||
erpnext.patches.v12_0.create_taxable_value_field
|
||||
erpnext.patches.v12_0.add_gst_category_in_delivery_note
|
||||
erpnext.patches.v12_0.purchase_receipt_status
|
||||
erpnext.patches.v13_0.fix_non_unique_represents_company
|
||||
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022
|
||||
erpnext.patches.v13_0.update_shipment_status
|
||||
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
|
||||
erpnext.patches.v12_0.add_ewaybill_validity_field
|
||||
erpnext.patches.v13_0.set_pos_closing_as_failed
|
||||
erpnext.patches.v13_0.rename_stop_to_send_birthday_reminders
|
||||
execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True)
|
||||
@ -274,9 +251,7 @@ erpnext.patches.v13_0.update_job_card_details
|
||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||
erpnext.patches.v13_0.update_amt_in_work_order_required_items
|
||||
erpnext.patches.v12_0.show_einvoice_irn_cancelled_field
|
||||
erpnext.patches.v13_0.delete_orphaned_tables
|
||||
erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16
|
||||
erpnext.patches.v13_0.update_tds_check_field #3
|
||||
erpnext.patches.v13_0.add_custom_field_for_south_africa #2
|
||||
erpnext.patches.v13_0.update_recipient_email_digest
|
||||
@ -297,9 +272,7 @@ erpnext.patches.v13_0.make_homepage_products_website_items
|
||||
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
|
||||
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
|
||||
erpnext.patches.v14_0.update_opportunity_currency_fields
|
||||
erpnext.patches.v13_0.gst_fields_for_pos_invoice
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
|
||||
erpnext.patches.v13_0.trim_sales_invoice_custom_field_length
|
||||
erpnext.patches.v13_0.create_custom_field_for_finance_book
|
||||
erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2
|
||||
erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry
|
||||
@ -313,7 +286,6 @@ erpnext.patches.v12_0.update_production_plan_status
|
||||
erpnext.patches.v13_0.healthcare_deprecation_warning
|
||||
erpnext.patches.v13_0.item_naming_series_not_mandatory
|
||||
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
||||
erpnext.patches.v13_0.create_pan_field_for_india #2
|
||||
erpnext.patches.v13_0.fetch_thumbnail_in_website_items
|
||||
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
|
||||
erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022
|
||||
@ -321,7 +293,6 @@ erpnext.patches.v14_0.migrate_crm_settings
|
||||
erpnext.patches.v13_0.rename_ksa_qr_field
|
||||
erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty
|
||||
erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
|
||||
erpnext.patches.v13_0.update_tax_category_for_rcm
|
||||
erpnext.patches.v14_0.set_payroll_cost_centers
|
||||
erpnext.patches.v13_0.agriculture_deprecation_warning
|
||||
erpnext.patches.v13_0.hospitality_deprecation_warning
|
||||
@ -330,6 +301,7 @@ erpnext.patches.v13_0.delete_bank_reconciliation_detail
|
||||
erpnext.patches.v13_0.enable_provisional_accounting
|
||||
erpnext.patches.v13_0.non_profit_deprecation_warning
|
||||
erpnext.patches.v13_0.enable_ksa_vat_docs #1
|
||||
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
||||
|
||||
[post_model_sync]
|
||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||
@ -344,9 +316,7 @@ erpnext.patches.v14_0.delete_education_doctypes
|
||||
erpnext.patches.v14_0.delete_datev_doctypes
|
||||
erpnext.patches.v14_0.rearrange_company_fields
|
||||
erpnext.patches.v14_0.update_leave_notification_template
|
||||
erpnext.patches.v14_0.restore_einvoice_fields
|
||||
erpnext.patches.v13_0.update_sane_transfer_against
|
||||
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
|
||||
erpnext.patches.v14_0.migrate_cost_center_allocations
|
||||
erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template
|
||||
erpnext.patches.v13_0.shopping_cart_to_ecommerce
|
||||
@ -363,7 +333,6 @@ erpnext.patches.v13_0.add_cost_center_in_loans
|
||||
erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
|
||||
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
|
||||
erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
|
||||
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
|
||||
erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
|
||||
erpnext.patches.v13_0.change_default_item_manufacturer_fieldtype
|
||||
erpnext.patches.v13_0.requeue_recoverable_reposts
|
||||
@ -374,5 +343,7 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
||||
execute:frappe.delete_doc("DocType", "Naming Series")
|
||||
erpnext.patches.v13_0.set_payroll_entry_status
|
||||
erpnext.patches.v13_0.job_card_status_on_hold
|
||||
erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
erpnext.patches.v14_0.crm_ux_cleanup
|
||||
erpnext.patches.v14_0.remove_india_localisation
|
||||
|
@ -1,13 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import add_permissions
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc("regional", "doctype", "lower_deduction_certificate")
|
||||
frappe.reload_doc("regional", "doctype", "gstr_3b_report")
|
||||
add_permissions()
|
@ -1,11 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
make_custom_fields()
|
@ -1,98 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
frappe.reload_doc("Payroll", "doctype", "Employee Tax Exemption Declaration")
|
||||
frappe.reload_doc("Payroll", "doctype", "Employee Tax Exemption Proof Submission")
|
||||
frappe.reload_doc("hr", "doctype", "Employee Grade")
|
||||
frappe.reload_doc("hr", "doctype", "Leave Policy")
|
||||
|
||||
frappe.reload_doc("accounts", "doctype", "Bank Account")
|
||||
frappe.reload_doc("accounts", "doctype", "Tax Withholding Category")
|
||||
frappe.reload_doc("accounts", "doctype", "Allowed To Transact With")
|
||||
frappe.reload_doc("accounts", "doctype", "Finance Book")
|
||||
frappe.reload_doc("accounts", "doctype", "Loyalty Program")
|
||||
|
||||
frappe.reload_doc("stock", "doctype", "Item Barcode")
|
||||
|
||||
make_custom_fields()
|
||||
|
||||
frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges")
|
||||
frappe.reload_doc("accounts", "doctype", "purchase_taxes_and_charges")
|
||||
frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges_template")
|
||||
frappe.reload_doc("accounts", "doctype", "purchase_taxes_and_charges_template")
|
||||
|
||||
# set is_inter_state in Taxes And Charges Templates
|
||||
if frappe.db.has_column(
|
||||
"Sales Taxes and Charges Template", "is_inter_state"
|
||||
) and frappe.db.has_column("Purchase Taxes and Charges Template", "is_inter_state"):
|
||||
|
||||
igst_accounts = set(
|
||||
frappe.db.sql_list(
|
||||
'''SELECT igst_account from `tabGST Account` WHERE parent = "GST Settings"'''
|
||||
)
|
||||
)
|
||||
cgst_accounts = set(
|
||||
frappe.db.sql_list(
|
||||
'''SELECT cgst_account FROM `tabGST Account` WHERE parenttype = "GST Settings"'''
|
||||
)
|
||||
)
|
||||
|
||||
when_then_sales = get_formatted_data("Sales Taxes and Charges", igst_accounts, cgst_accounts)
|
||||
when_then_purchase = get_formatted_data(
|
||||
"Purchase Taxes and Charges", igst_accounts, cgst_accounts
|
||||
)
|
||||
|
||||
if when_then_sales:
|
||||
frappe.db.sql(
|
||||
"""update `tabSales Taxes and Charges Template`
|
||||
set is_inter_state = Case {when_then} Else 0 End
|
||||
""".format(
|
||||
when_then=" ".join(when_then_sales)
|
||||
)
|
||||
)
|
||||
|
||||
if when_then_purchase:
|
||||
frappe.db.sql(
|
||||
"""update `tabPurchase Taxes and Charges Template`
|
||||
set is_inter_state = Case {when_then} Else 0 End
|
||||
""".format(
|
||||
when_then=" ".join(when_then_purchase)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_formatted_data(doctype, igst_accounts, cgst_accounts):
|
||||
# fetch all the rows data from child table
|
||||
all_details = frappe.db.sql(
|
||||
'''
|
||||
select parent, account_head from `tab{doctype}`
|
||||
where parenttype="{doctype} Template"'''.format(
|
||||
doctype=doctype
|
||||
),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
# group the data in the form "parent: [list of accounts]""
|
||||
group_detail = {}
|
||||
for i in all_details:
|
||||
if not i["parent"] in group_detail:
|
||||
group_detail[i["parent"]] = []
|
||||
for j in all_details:
|
||||
if i["parent"] == j["parent"]:
|
||||
group_detail[i["parent"]].append(j["account_head"])
|
||||
|
||||
# form when_then condition based on - if list of accounts for a document
|
||||
# matches any account in igst_accounts list and not matches any in cgst_accounts list
|
||||
when_then = []
|
||||
for i in group_detail:
|
||||
temp = set(group_detail[i])
|
||||
if not temp.isdisjoint(igst_accounts) and temp.isdisjoint(cgst_accounts):
|
||||
when_then.append("""When name='{name}' Then 1""".format(name=i))
|
||||
|
||||
return when_then
|
@ -12,7 +12,6 @@ doctype_series_map = {
|
||||
"Attendance": "HR-ATT-.YYYY.-",
|
||||
"Auto Repeat": "SYS-ARP-.YYYY.-",
|
||||
"Blanket Order": "MFG-BLR-.YYYY.-",
|
||||
"C-Form": "ACC-CF-.YYYY.-",
|
||||
"Campaign": "SAL-CAM-.YYYY.-",
|
||||
"Course Schedule": "EDU-CSH-.YYYY.-",
|
||||
"Customer": "CUST-.YYYY.-",
|
||||
|
@ -1,64 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_html
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.db.sql_list("select name from tabCompany where country = 'India'")
|
||||
if not company:
|
||||
return
|
||||
|
||||
doctypes = [
|
||||
"Quotation",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Sales Invoice",
|
||||
"Supplier Quotation",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
]
|
||||
|
||||
for dt in doctypes:
|
||||
date_field = "posting_date"
|
||||
if dt in ["Quotation", "Sales Order", "Supplier Quotation", "Purchase Order"]:
|
||||
date_field = "transaction_date"
|
||||
|
||||
transactions = frappe.db.sql(
|
||||
"""
|
||||
select dt.name, dt_item.name as child_name
|
||||
from `tab{dt}` dt, `tab{dt} Item` dt_item
|
||||
where dt.name = dt_item.parent
|
||||
and dt.`{date_field}` > '2018-06-01'
|
||||
and dt.docstatus = 1
|
||||
and ifnull(dt_item.gst_hsn_code, '') = ''
|
||||
and ifnull(dt_item.item_code, '') != ''
|
||||
and dt.company in ({company})
|
||||
""".format(
|
||||
dt=dt, date_field=date_field, company=", ".join(["%s"] * len(company))
|
||||
),
|
||||
tuple(company),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if not transactions:
|
||||
continue
|
||||
|
||||
transaction_rows_name = [d.child_name for d in transactions]
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
update `tab{dt} Item` dt_item
|
||||
set dt_item.gst_hsn_code = (select gst_hsn_code from tabItem where name=dt_item.item_code)
|
||||
where dt_item.name in ({rows_name})
|
||||
""".format(
|
||||
dt=dt, rows_name=", ".join(["%s"] * len(transaction_rows_name))
|
||||
),
|
||||
tuple(transaction_rows_name),
|
||||
)
|
||||
|
||||
parent = set([d.name for d in transactions])
|
||||
for t in list(parent):
|
||||
trans_doc = frappe.get_doc(dt, t)
|
||||
hsnwise_tax = get_itemised_tax_breakup_html(trans_doc)
|
||||
frappe.db.set_value(dt, t, "other_charges_calculation", hsnwise_tax, update_modified=False)
|
@ -1,21 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company or not frappe.db.count("E Invoice User"):
|
||||
return
|
||||
|
||||
frappe.reload_doc("regional", "doctype", "e_invoice_user")
|
||||
for creds in frappe.db.get_all("E Invoice User", fields=["name", "gstin"]):
|
||||
company_name = frappe.db.sql(
|
||||
"""
|
||||
select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
|
||||
where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
|
||||
""",
|
||||
(creds.get("gstin")),
|
||||
)
|
||||
if company_name and len(company_name) > 0:
|
||||
frappe.db.set_value("E Invoice User", creds.get("name"), "company", company_name[0][0])
|
@ -1,160 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# move hidden einvoice fields to a different section
|
||||
custom_fields = {
|
||||
"Sales Invoice": [
|
||||
dict(
|
||||
fieldname="einvoice_section",
|
||||
label="E-Invoice Fields",
|
||||
fieldtype="Section Break",
|
||||
insert_after="gst_vehicle_type",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="ack_no",
|
||||
label="Ack. No.",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
hidden=1,
|
||||
insert_after="einvoice_section",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="ack_date",
|
||||
label="Ack. Date",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
hidden=1,
|
||||
insert_after="ack_no",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="irn_cancel_date",
|
||||
label="Cancel Date",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
hidden=1,
|
||||
insert_after="ack_date",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="signed_einvoice",
|
||||
label="Signed E-Invoice",
|
||||
fieldtype="Code",
|
||||
options="JSON",
|
||||
hidden=1,
|
||||
insert_after="irn_cancel_date",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="signed_qr_code",
|
||||
label="Signed QRCode",
|
||||
fieldtype="Code",
|
||||
options="JSON",
|
||||
hidden=1,
|
||||
insert_after="signed_einvoice",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="qrcode_image",
|
||||
label="QRCode",
|
||||
fieldtype="Attach Image",
|
||||
hidden=1,
|
||||
insert_after="signed_qr_code",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="einvoice_status",
|
||||
label="E-Invoice Status",
|
||||
fieldtype="Select",
|
||||
insert_after="qrcode_image",
|
||||
options="\nPending\nGenerated\nCancelled\nFailed",
|
||||
default=None,
|
||||
hidden=1,
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="failure_description",
|
||||
label="E-Invoice Failure Description",
|
||||
fieldtype="Code",
|
||||
options="JSON",
|
||||
hidden=1,
|
||||
insert_after="einvoice_status",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
|
||||
if frappe.db.exists("E Invoice Settings") and frappe.db.get_single_value(
|
||||
"E Invoice Settings", "enable"
|
||||
):
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
|
||||
WHERE
|
||||
posting_date >= '2021-04-01'
|
||||
AND ifnull(irn, '') = ''
|
||||
AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
|
||||
AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
|
||||
"""
|
||||
)
|
||||
|
||||
# set appropriate statuses
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
|
||||
WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0"""
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
|
||||
WHERE ifnull(irn_cancelled, 0) = 1"""
|
||||
)
|
||||
|
||||
# set correct acknowledgement in e-invoices
|
||||
einvoices = frappe.get_all("Sales Invoice", {"irn": ["is", "set"]}, ["name", "signed_einvoice"])
|
||||
|
||||
if einvoices:
|
||||
for inv in einvoices:
|
||||
signed_einvoice = inv.get("signed_einvoice")
|
||||
if signed_einvoice:
|
||||
signed_einvoice = json.loads(signed_einvoice)
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice",
|
||||
inv.get("name"),
|
||||
"ack_no",
|
||||
signed_einvoice.get("AckNo"),
|
||||
update_modified=False,
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice",
|
||||
inv.get("name"),
|
||||
"ack_date",
|
||||
signed_einvoice.get("AckDt"),
|
||||
update_modified=False,
|
||||
)
|
@ -1,20 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
if frappe.db.exists("Report", "E-Invoice Summary") and not frappe.db.get_value(
|
||||
"Custom Role", dict(report="E-Invoice Summary")
|
||||
):
|
||||
frappe.get_doc(
|
||||
dict(
|
||||
doctype="Custom Role",
|
||||
report="E-Invoice Summary",
|
||||
roles=[dict(role="Accounts User"), dict(role="Accounts Manager")],
|
||||
)
|
||||
).insert()
|
@ -1,23 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
|
||||
if not company:
|
||||
return
|
||||
|
||||
create_custom_field(
|
||||
"Delivery Note",
|
||||
{
|
||||
"fieldname": "ewaybill",
|
||||
"label": "E-Way Bill No.",
|
||||
"fieldtype": "Data",
|
||||
"depends_on": "eval:(doc.docstatus === 1)",
|
||||
"allow_on_submit": 1,
|
||||
"insert_after": "customer_name_in_arabic",
|
||||
"translatable": 0,
|
||||
"owner": "Administrator",
|
||||
},
|
||||
)
|
@ -1,27 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = {
|
||||
"Sales Invoice": [
|
||||
dict(
|
||||
fieldname="eway_bill_validity",
|
||||
label="E-Way Bill Validity",
|
||||
fieldtype="Data",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
depends_on="ewaybill",
|
||||
read_only=1,
|
||||
allow_on_submit=1,
|
||||
insert_after="ewaybill",
|
||||
)
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -1,42 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
make_custom_fields()
|
||||
|
||||
frappe.reload_doctype("Tax Category")
|
||||
frappe.reload_doctype("Sales Taxes and Charges Template")
|
||||
frappe.reload_doctype("Purchase Taxes and Charges Template")
|
||||
|
||||
# Create tax category with inter state field checked
|
||||
tax_category = frappe.db.get_value("Tax Category", {"name": "OUT OF STATE"}, "name")
|
||||
|
||||
if not tax_category:
|
||||
inter_state_category = frappe.get_doc(
|
||||
{"doctype": "Tax Category", "title": "OUT OF STATE", "is_inter_state": 1}
|
||||
).insert()
|
||||
|
||||
tax_category = inter_state_category.name
|
||||
|
||||
for doctype in ("Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"):
|
||||
if not frappe.get_meta(doctype).has_field("is_inter_state"):
|
||||
continue
|
||||
|
||||
template = frappe.db.get_value(doctype, {"is_inter_state": 1, "disabled": 0}, ["name"])
|
||||
if template:
|
||||
frappe.db.set_value(doctype, template, "tax_category", tax_category)
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
DELETE FROM `tabCustom Field`
|
||||
WHERE fieldname = 'is_inter_state'
|
||||
AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template')
|
||||
"""
|
||||
)
|
@ -1,25 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = {
|
||||
"Delivery Note": [
|
||||
dict(
|
||||
fieldname="gst_category",
|
||||
label="GST Category",
|
||||
fieldtype="Select",
|
||||
insert_after="gst_vehicle_type",
|
||||
print_hide=1,
|
||||
options="\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders",
|
||||
fetch_from="customer.gst_category",
|
||||
fetch_if_empty=1,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -1,19 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india import states
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = ["Address-gst_state", "Tax Category-gst_state"]
|
||||
|
||||
# Update options in gst_state custom fields
|
||||
for field in custom_fields:
|
||||
if frappe.db.exists("Custom Field", field):
|
||||
gst_state_field = frappe.get_doc("Custom Field", field)
|
||||
gst_state_field.options = "\n".join(states)
|
||||
gst_state_field.save()
|
@ -1,167 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
|
||||
from erpnext.regional.india.utils import get_gst_accounts
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"}, fields=["name"])
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc("regional", "doctype", "gst_settings")
|
||||
frappe.reload_doc("accounts", "doctype", "gst_account")
|
||||
|
||||
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + [
|
||||
"Reversal Of ITC"
|
||||
]
|
||||
make_property_setter(
|
||||
"Journal Entry", "voucher_type", "options", "\n".join(journal_entry_types), ""
|
||||
)
|
||||
|
||||
custom_fields = {
|
||||
"Journal Entry": [
|
||||
dict(
|
||||
fieldname="reversal_type",
|
||||
label="Reversal Type",
|
||||
fieldtype="Select",
|
||||
insert_after="voucher_type",
|
||||
print_hide=1,
|
||||
options="As per rules 42 & 43 of CGST Rules\nOthers",
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
),
|
||||
dict(
|
||||
fieldname="company_address",
|
||||
label="Company Address",
|
||||
fieldtype="Link",
|
||||
options="Address",
|
||||
insert_after="reversal_type",
|
||||
print_hide=1,
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
),
|
||||
dict(
|
||||
fieldname="company_gstin",
|
||||
label="Company GSTIN",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
insert_after="company_address",
|
||||
print_hide=1,
|
||||
fetch_from="company_address.gstin",
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
),
|
||||
],
|
||||
"Purchase Invoice": [
|
||||
dict(
|
||||
fieldname="eligibility_for_itc",
|
||||
label="Eligibility For ITC",
|
||||
fieldtype="Select",
|
||||
insert_after="reason_for_issuing_document",
|
||||
print_hide=1,
|
||||
options="Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC",
|
||||
default="All Other ITC",
|
||||
)
|
||||
],
|
||||
"Purchase Invoice Item": [
|
||||
dict(
|
||||
fieldname="taxable_value",
|
||||
label="Taxable Value",
|
||||
fieldtype="Currency",
|
||||
insert_after="base_net_amount",
|
||||
hidden=1,
|
||||
options="Company:company:default_currency",
|
||||
print_hide=1,
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
|
||||
# Patch ITC Availed fields from Data to Currency
|
||||
# Patch Availed ITC for current fiscal_year
|
||||
|
||||
gst_accounts = get_gst_accounts(only_non_reverse_charge=1)
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency'
|
||||
WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax',
|
||||
'itc_cess_amount')
|
||||
"""
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0'
|
||||
WHERE trim(coalesce(itc_integrated_tax, '')) = '' """
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabPurchase Invoice` set itc_state_tax = '0'
|
||||
WHERE trim(coalesce(itc_state_tax, '')) = '' """
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabPurchase Invoice` set itc_central_tax = '0'
|
||||
WHERE trim(coalesce(itc_central_tax, '')) = '' """
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0'
|
||||
WHERE trim(coalesce(itc_cess_amount, '')) = '' """
|
||||
)
|
||||
|
||||
# Get purchase invoices
|
||||
invoices = frappe.get_all(
|
||||
"Purchase Invoice",
|
||||
{"posting_date": (">=", "2021-04-01"), "eligibility_for_itc": ("!=", "Ineligible")},
|
||||
["name"],
|
||||
)
|
||||
|
||||
amount_map = {}
|
||||
|
||||
if invoices:
|
||||
invoice_list = set([d.name for d in invoices])
|
||||
|
||||
# Get GST applied
|
||||
amounts = frappe.db.sql(
|
||||
"""
|
||||
SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount
|
||||
FROM `tabPurchase Taxes and Charges`
|
||||
where parent in %s
|
||||
GROUP BY parent, account_head
|
||||
""",
|
||||
(invoice_list),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
for d in amounts:
|
||||
amount_map.setdefault(
|
||||
d.parent,
|
||||
{"itc_integrated_tax": 0, "itc_state_tax": 0, "itc_central_tax": 0, "itc_cess_amount": 0},
|
||||
)
|
||||
if not gst_accounts:
|
||||
continue
|
||||
|
||||
if d.account_head in gst_accounts.get("igst_account"):
|
||||
amount_map[d.parent]["itc_integrated_tax"] += d.amount
|
||||
if d.account_head in gst_accounts.get("cgst_account"):
|
||||
amount_map[d.parent]["itc_central_tax"] += d.amount
|
||||
if d.account_head in gst_accounts.get("sgst_account"):
|
||||
amount_map[d.parent]["itc_state_tax"] += d.amount
|
||||
if d.account_head in gst_accounts.get("cess_account"):
|
||||
amount_map[d.parent]["itc_cess_amount"] += d.amount
|
||||
|
||||
for invoice, values in amount_map.items():
|
||||
frappe.db.set_value(
|
||||
"Purchase Invoice",
|
||||
invoice,
|
||||
{
|
||||
"itc_integrated_tax": values.get("itc_integrated_tax"),
|
||||
"itc_central_tax": values.get("itc_central_tax"),
|
||||
"itc_state_tax": values["itc_state_tax"],
|
||||
"itc_cess_amount": values["itc_cess_amount"],
|
||||
},
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = {
|
||||
"Sales Invoice Item": [
|
||||
dict(
|
||||
fieldname="taxable_value",
|
||||
label="Taxable Value",
|
||||
fieldtype="Currency",
|
||||
insert_after="base_net_amount",
|
||||
hidden=1,
|
||||
options="Company:company:default_currency",
|
||||
print_hide=1,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -1,66 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc("accounts", "doctype", "Tax Category")
|
||||
|
||||
make_custom_fields()
|
||||
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
has_column = frappe.db.has_column(doctype, "invoice_type")
|
||||
|
||||
if has_column:
|
||||
update_map = {
|
||||
"Regular": "Registered Regular",
|
||||
"Export": "Overseas",
|
||||
"SEZ": "SEZ",
|
||||
"Deemed Export": "Deemed Export",
|
||||
}
|
||||
|
||||
for old, new in update_map.items():
|
||||
frappe.db.sql(
|
||||
"UPDATE `tab{doctype}` SET gst_category = %s where invoice_type = %s".format(doctype=doctype),
|
||||
(new, old),
|
||||
) # nosec
|
||||
|
||||
frappe.delete_doc("Custom Field", "Sales Invoice-invoice_type")
|
||||
frappe.delete_doc("Custom Field", "Purchase Invoice-invoice_type")
|
||||
|
||||
itc_update_map = {
|
||||
"ineligible": "Ineligible",
|
||||
"input service": "Input Service Distributor",
|
||||
"capital goods": "Import Of Capital Goods",
|
||||
"input": "All Other ITC",
|
||||
}
|
||||
|
||||
has_gst_fields = frappe.db.has_column("Purchase Invoice", "eligibility_for_itc")
|
||||
|
||||
if has_gst_fields:
|
||||
for old, new in itc_update_map.items():
|
||||
frappe.db.sql(
|
||||
"UPDATE `tabPurchase Invoice` SET eligibility_for_itc = %s where eligibility_for_itc = %s ",
|
||||
(new, old),
|
||||
)
|
||||
|
||||
for doctype in ["Customer", "Supplier"]:
|
||||
|
||||
frappe.db.sql(
|
||||
""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Registered Regular"
|
||||
where t3.link_name = t1.name and t3.parent = t2.name and t2.gstin IS NOT NULL and t2.gstin != '' """.format(
|
||||
doctype=doctype
|
||||
)
|
||||
) # nosec
|
||||
|
||||
frappe.db.sql(
|
||||
""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Overseas"
|
||||
where t3.link_name = t1.name and t3.parent = t2.name and t2.country != 'India' """.format(
|
||||
doctype=doctype
|
||||
)
|
||||
) # nosec
|
@ -1,134 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
from erpnext.regional.india.setup import add_permissions, add_print_formats
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc("custom", "doctype", "custom_field")
|
||||
frappe.reload_doc("regional", "doctype", "e_invoice_settings")
|
||||
custom_fields = {
|
||||
"Sales Invoice": [
|
||||
dict(
|
||||
fieldname="irn",
|
||||
label="IRN",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
insert_after="customer",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0',
|
||||
),
|
||||
dict(
|
||||
fieldname="ack_no",
|
||||
label="Ack. No.",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
hidden=1,
|
||||
insert_after="irn",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="ack_date",
|
||||
label="Ack. Date",
|
||||
fieldtype="Data",
|
||||
read_only=1,
|
||||
hidden=1,
|
||||
insert_after="ack_no",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="irn_cancelled",
|
||||
label="IRN Cancelled",
|
||||
fieldtype="Check",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
depends_on="eval:(doc.irn_cancelled === 1)",
|
||||
read_only=1,
|
||||
allow_on_submit=1,
|
||||
insert_after="customer",
|
||||
),
|
||||
dict(
|
||||
fieldname="eway_bill_cancelled",
|
||||
label="E-Way Bill Cancelled",
|
||||
fieldtype="Check",
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
depends_on="eval:(doc.eway_bill_cancelled === 1)",
|
||||
read_only=1,
|
||||
allow_on_submit=1,
|
||||
insert_after="customer",
|
||||
),
|
||||
dict(
|
||||
fieldname="signed_einvoice",
|
||||
fieldtype="Code",
|
||||
options="JSON",
|
||||
hidden=1,
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="signed_qr_code",
|
||||
fieldtype="Code",
|
||||
options="JSON",
|
||||
hidden=1,
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="qrcode_image",
|
||||
label="QRCode",
|
||||
fieldtype="Attach Image",
|
||||
hidden=1,
|
||||
no_copy=1,
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
add_permissions()
|
||||
add_print_formats()
|
||||
|
||||
einvoice_cond = (
|
||||
'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
|
||||
)
|
||||
t = {
|
||||
"mode_of_transport": [{"default": None}],
|
||||
"distance": [{"mandatory_depends_on": f"eval:{einvoice_cond} && doc.transporter"}],
|
||||
"gst_vehicle_type": [
|
||||
{"mandatory_depends_on": f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}
|
||||
],
|
||||
"lr_date": [
|
||||
{
|
||||
"mandatory_depends_on": f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'
|
||||
}
|
||||
],
|
||||
"lr_no": [
|
||||
{
|
||||
"mandatory_depends_on": f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'
|
||||
}
|
||||
],
|
||||
"vehicle_no": [
|
||||
{"mandatory_depends_on": f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}
|
||||
],
|
||||
"ewaybill": [
|
||||
{"read_only_depends_on": "eval:doc.irn && doc.ewaybill"},
|
||||
{"depends_on": "eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)"},
|
||||
],
|
||||
}
|
||||
|
||||
for field, conditions in t.items():
|
||||
for c in conditions:
|
||||
[(prop, value)] = c.items()
|
||||
frappe.db.set_value("Custom Field", {"fieldname": field}, prop, value)
|
@ -1,16 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
irn_cancelled_field = frappe.db.exists(
|
||||
"Custom Field", {"dt": "Sales Invoice", "fieldname": "irn_cancelled"}
|
||||
)
|
||||
if irn_cancelled_field:
|
||||
frappe.db.set_value("Custom Field", irn_cancelled_field, "depends_on", "eval: doc.irn")
|
||||
frappe.db.set_value("Custom Field", irn_cancelled_field, "read_only", 0)
|
@ -1,14 +0,0 @@
|
||||
# Copyright (c) 2020, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.address_template.setup import set_up_address_templates
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.get_value("Company", {"country": "India"}, "name"):
|
||||
address_template = frappe.db.get_value("Address Template", "India", "template")
|
||||
if not address_template or "gstin" not in address_template:
|
||||
set_up_address_templates(default_country="India")
|
@ -1,29 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
|
||||
if not company:
|
||||
return
|
||||
|
||||
field = frappe.db.get_value("Custom Field", {"dt": "Sales Invoice", "fieldname": "ewaybill"})
|
||||
|
||||
if field:
|
||||
ewaybill_field = frappe.get_doc("Custom Field", field)
|
||||
|
||||
ewaybill_field.flags.ignore_validate = True
|
||||
|
||||
ewaybill_field.update(
|
||||
{
|
||||
"fieldname": "ewaybill",
|
||||
"label": "e-Way Bill No.",
|
||||
"fieldtype": "Data",
|
||||
"depends_on": "eval:(doc.docstatus === 1)",
|
||||
"allow_on_submit": 1,
|
||||
"insert_after": "tax_id",
|
||||
"translatable": 0,
|
||||
}
|
||||
)
|
||||
|
||||
ewaybill_field.save()
|
@ -1,23 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.db.sql(
|
||||
""" UPDATE `tabSales Invoice` set gst_category = 'Unregistered'
|
||||
where gst_category = 'Registered Regular'
|
||||
and ifnull(customer_gstin, '')=''
|
||||
and ifnull(billing_address_gstin,'')=''
|
||||
"""
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
""" UPDATE `tabPurchase Invoice` set gst_category = 'Unregistered'
|
||||
where gst_category = 'Registered Regular'
|
||||
and ifnull(supplier_gstin, '')=''
|
||||
"""
|
||||
)
|
@ -1,26 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india import states
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# Update options in gst_state custom field
|
||||
gst_state = frappe.get_doc("Custom Field", "Address-gst_state")
|
||||
gst_state.options = "\n".join(states)
|
||||
gst_state.save()
|
||||
|
||||
# Update gst_state and state code in existing address
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE `tabAddress`
|
||||
SET
|
||||
gst_state = 'Dadra and Nagar Haveli and Daman and Diu',
|
||||
gst_state_number = 26
|
||||
WHERE gst_state = 'Daman and Diu'
|
||||
"""
|
||||
)
|
@ -64,4 +64,8 @@ def delete_and_patch_duplicate_bins():
|
||||
bin.update(qty_dict)
|
||||
bin.update_reserved_qty_for_production()
|
||||
bin.update_reserved_qty_for_sub_contracting()
|
||||
if frappe.db.count(
|
||||
"Purchase Order", {"status": ["!=", "Completed"], "is_old_subcontracting_flow": 1}
|
||||
):
|
||||
bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
|
||||
bin.db_update()
|
||||
|
@ -1,53 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"}, fields=["name"])
|
||||
if not company:
|
||||
return
|
||||
|
||||
sales_invoice_gst_fields = [
|
||||
dict(
|
||||
fieldname="billing_address_gstin",
|
||||
label="Billing Address GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_address",
|
||||
read_only=1,
|
||||
fetch_from="customer_address.gstin",
|
||||
print_hide=1,
|
||||
length=15,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_gstin",
|
||||
label="Customer GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="shipping_address_name",
|
||||
fetch_from="shipping_address_name.gstin",
|
||||
print_hide=1,
|
||||
length=15,
|
||||
),
|
||||
dict(
|
||||
fieldname="place_of_supply",
|
||||
label="Place of Supply",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
length=50,
|
||||
),
|
||||
dict(
|
||||
fieldname="company_gstin",
|
||||
label="Company GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="company_address",
|
||||
fetch_from="company_address.gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
length=15,
|
||||
),
|
||||
]
|
||||
|
||||
custom_fields = {"Quotation": sales_invoice_gst_fields}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -2,79 +2,19 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
# Patch kept for users outside India
|
||||
def execute():
|
||||
frappe.reload_doc("accounts", "doctype", "advance_taxes_and_charges")
|
||||
frappe.reload_doc("accounts", "doctype", "payment_entry")
|
||||
|
||||
if frappe.db.exists("Company", {"country": "India"}):
|
||||
custom_fields = {
|
||||
"Payment Entry": [
|
||||
dict(
|
||||
fieldname="gst_section",
|
||||
label="GST Details",
|
||||
fieldtype="Section Break",
|
||||
insert_after="deductions",
|
||||
print_hide=1,
|
||||
collapsible=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="company_address",
|
||||
label="Company Address",
|
||||
fieldtype="Link",
|
||||
insert_after="gst_section",
|
||||
print_hide=1,
|
||||
options="Address",
|
||||
),
|
||||
dict(
|
||||
fieldname="company_gstin",
|
||||
label="Company GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="company_address",
|
||||
fetch_from="company_address.gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="place_of_supply",
|
||||
label="Place of Supply",
|
||||
fieldtype="Data",
|
||||
insert_after="company_gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_address",
|
||||
label="Customer Address",
|
||||
fieldtype="Link",
|
||||
insert_after="place_of_supply",
|
||||
print_hide=1,
|
||||
options="Address",
|
||||
depends_on='eval:doc.party_type == "Customer"',
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_gstin",
|
||||
label="Customer GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_address",
|
||||
fetch_from="customer_address.gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
]
|
||||
}
|
||||
return
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
else:
|
||||
fields = [
|
||||
"gst_section",
|
||||
"company_address",
|
||||
"company_gstin",
|
||||
"place_of_supply",
|
||||
"customer_address",
|
||||
"customer_gstin",
|
||||
]
|
||||
for field in fields:
|
||||
frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")
|
||||
for field in (
|
||||
"gst_section",
|
||||
"company_address",
|
||||
"company_gstin",
|
||||
"place_of_supply",
|
||||
"customer_address",
|
||||
"customer_gstin",
|
||||
):
|
||||
frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")
|
||||
|
@ -1,19 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("buying", "doctype", "supplier", force=True)
|
||||
frappe.reload_doc("selling", "doctype", "customer", force=True)
|
||||
frappe.reload_doc("core", "doctype", "doctype", force=True)
|
||||
|
||||
custom_fields = {
|
||||
"Supplier": [
|
||||
{"fieldname": "pan", "label": "PAN", "fieldtype": "Data", "insert_after": "supplier_type"}
|
||||
],
|
||||
"Customer": [
|
||||
{"fieldname": "pan", "label": "PAN", "fieldtype": "Data", "insert_after": "customer_type"}
|
||||
],
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -1,87 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"}, fields=["name"])
|
||||
if not company:
|
||||
return
|
||||
|
||||
hsn_sac_field = dict(
|
||||
fieldname="gst_hsn_code",
|
||||
label="HSN/SAC",
|
||||
fieldtype="Data",
|
||||
fetch_from="item_code.gst_hsn_code",
|
||||
insert_after="description",
|
||||
allow_on_submit=1,
|
||||
print_hide=1,
|
||||
fetch_if_empty=1,
|
||||
)
|
||||
nil_rated_exempt = dict(
|
||||
fieldname="is_nil_exempt",
|
||||
label="Is Nil Rated or Exempted",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_nil_exempt",
|
||||
insert_after="gst_hsn_code",
|
||||
print_hide=1,
|
||||
)
|
||||
is_non_gst = dict(
|
||||
fieldname="is_non_gst",
|
||||
label="Is Non GST",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_non_gst",
|
||||
insert_after="is_nil_exempt",
|
||||
print_hide=1,
|
||||
)
|
||||
taxable_value = dict(
|
||||
fieldname="taxable_value",
|
||||
label="Taxable Value",
|
||||
fieldtype="Currency",
|
||||
insert_after="base_net_amount",
|
||||
hidden=1,
|
||||
options="Company:company:default_currency",
|
||||
print_hide=1,
|
||||
)
|
||||
sales_invoice_gst_fields = [
|
||||
dict(
|
||||
fieldname="billing_address_gstin",
|
||||
label="Billing Address GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_address",
|
||||
read_only=1,
|
||||
fetch_from="customer_address.gstin",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_gstin",
|
||||
label="Customer GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="shipping_address_name",
|
||||
fetch_from="shipping_address_name.gstin",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="place_of_supply",
|
||||
label="Place of Supply",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="company_gstin",
|
||||
label="Company GSTIN",
|
||||
fieldtype="Data",
|
||||
insert_after="company_address",
|
||||
fetch_from="company_address.gstin",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
]
|
||||
|
||||
custom_fields = {
|
||||
"POS Invoice": sales_invoice_gst_fields,
|
||||
"POS Invoice Item": [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -15,6 +15,8 @@ def execute():
|
||||
("accounts", "sales_invoice_item"),
|
||||
("accounts", "purchase_invoice_item"),
|
||||
("buying", "purchase_receipt_item_supplied"),
|
||||
("subcontracting", "subcontracting_receipt_item"),
|
||||
("subcontracting", "subcontracting_receipt_supplied_item"),
|
||||
]
|
||||
|
||||
for module, doctype in doctypes_to_reload:
|
||||
|
@ -1,11 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import add_custom_roles_for_reports
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
add_custom_roles_for_reports()
|
@ -1,16 +0,0 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.get_all("Company", filters={"country": "India"}):
|
||||
frappe.reload_doc("accounts", "doctype", "POS Invoice")
|
||||
frappe.reload_doc("accounts", "doctype", "POS Invoice Item")
|
||||
|
||||
make_custom_fields()
|
||||
|
||||
if not frappe.db.exists("Party Type", "Donor"):
|
||||
frappe.get_doc(
|
||||
{"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"}
|
||||
).insert(ignore_permissions=True)
|
@ -0,0 +1,15 @@
|
||||
import click
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("Company", {"country": "India"}):
|
||||
return
|
||||
|
||||
click.secho(
|
||||
"India-specific regional features have been moved to a separate app"
|
||||
" and will be removed from ERPNext in Version 14."
|
||||
" Please install India Compliance after upgrading to Version 14:\n"
|
||||
"https://github.com/resilient-tech/india-compliance",
|
||||
fg="yellow",
|
||||
)
|
@ -1,16 +0,0 @@
|
||||
# Copyright (c) 2020, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import create_custom_fields, get_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all("Company", filters={"country": "India"})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = {"Sales Invoice": get_custom_fields().get("Sales Invoice")}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user