Merge pull request #39090 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
commit
d2fdce007e
@ -137,7 +137,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
|||||||
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
|
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
|
||||||
args: {
|
args: {
|
||||||
bank_account: frm.doc.bank_account,
|
bank_account: frm.doc.bank_account,
|
||||||
till_date: frm.doc.bank_statement_from_date,
|
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1)
|
||||||
},
|
},
|
||||||
callback: (response) => {
|
callback: (response) => {
|
||||||
frm.set_value("account_opening_balance", response.message);
|
frm.set_value("account_opening_balance", response.message);
|
||||||
|
@ -1,457 +1,152 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-06-18 16:51:49.994750",
|
"creation": "2018-06-18 16:51:49.994750",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"user",
|
||||||
|
"date",
|
||||||
|
"from_time",
|
||||||
|
"time",
|
||||||
|
"expense",
|
||||||
|
"custody",
|
||||||
|
"returns",
|
||||||
|
"outstanding_amount",
|
||||||
|
"payments",
|
||||||
|
"net_amount",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "POS-CLO-",
|
"default": "POS-CLO-",
|
||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Series",
|
"label": "Series",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "POS-CLO-",
|
"options": "POS-CLO-",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"precision": "",
|
|
||||||
"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": "user",
|
"fieldname": "user",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "User",
|
"label": "User",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "User",
|
"options": "User",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"remember_last_selected_value": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fieldname": "date",
|
"fieldname": "date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Date",
|
"label": "Date",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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": "from_time",
|
"fieldname": "from_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "From Time",
|
"label": "From Time",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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,
|
|
||||||
"default": "",
|
|
||||||
"fieldname": "time",
|
"fieldname": "time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "To Time",
|
"label": "To Time",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "expense",
|
"fieldname": "expense",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
"label": "Expense"
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Expense",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "custody",
|
"fieldname": "custody",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
"label": "Custody"
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Custody",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "returns",
|
"fieldname": "returns",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Returns",
|
"label": "Returns",
|
||||||
"length": 0,
|
"precision": "2"
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "2",
|
|
||||||
"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,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "outstanding_amount",
|
"fieldname": "outstanding_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"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": "Outstanding Amount",
|
"label": "Outstanding Amount",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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,
|
|
||||||
"default": "0.0",
|
|
||||||
"fieldname": "payments",
|
"fieldname": "payments",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Payments",
|
"label": "Payments",
|
||||||
"length": 0,
|
"options": "Cashier Closing Payments"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Cashier Closing Payments",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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": "net_amount",
|
"fieldname": "net_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Net Amount",
|
"label": "Net Amount",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"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",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"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": 0,
|
|
||||||
"label": "Amended From",
|
"label": "Amended From",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Cashier Closing",
|
"options": "Cashier Closing",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"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,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"issingle": 0,
|
"links": [],
|
||||||
"istable": 0,
|
"modified": "2023-12-28 13:15:46.858427",
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2019-02-19 08:35:24.157327",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Cashier Closing",
|
"name": "Cashier Closing",
|
||||||
"name_case": "",
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"states": [],
|
||||||
"track_seen": 0,
|
"track_changes": 1
|
||||||
"track_views": 0
|
}
|
||||||
}
|
|
@ -21,6 +21,7 @@
|
|||||||
"against_voucher_type",
|
"against_voucher_type",
|
||||||
"against_voucher",
|
"against_voucher",
|
||||||
"voucher_type",
|
"voucher_type",
|
||||||
|
"voucher_subtype",
|
||||||
"voucher_no",
|
"voucher_no",
|
||||||
"voucher_detail_no",
|
"voucher_detail_no",
|
||||||
"project",
|
"project",
|
||||||
@ -278,13 +279,18 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Credit Amount in Transaction Currency",
|
"label": "Credit Amount in Transaction Currency",
|
||||||
"options": "transaction_currency"
|
"options": "transaction_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_subtype",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Voucher Subtype"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-list",
|
"icon": "fa fa-list",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-16 21:38:44.072267",
|
"modified": "2023-12-18 15:38:14.006208",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "GL Entry",
|
"name": "GL Entry",
|
||||||
|
@ -39,6 +39,8 @@ class GLEntry(Document):
|
|||||||
account: DF.Link | None
|
account: DF.Link | None
|
||||||
account_currency: DF.Link | None
|
account_currency: DF.Link | None
|
||||||
against: DF.Text | None
|
against: DF.Text | None
|
||||||
|
against_link: DF.DynamicLink | None
|
||||||
|
against_type: DF.Link | None
|
||||||
against_voucher: DF.DynamicLink | None
|
against_voucher: DF.DynamicLink | None
|
||||||
against_voucher_type: DF.Link | None
|
against_voucher_type: DF.Link | None
|
||||||
company: DF.Link | None
|
company: DF.Link | None
|
||||||
@ -66,6 +68,7 @@ class GLEntry(Document):
|
|||||||
transaction_exchange_rate: DF.Float
|
transaction_exchange_rate: DF.Float
|
||||||
voucher_detail_no: DF.Data | None
|
voucher_detail_no: DF.Data | None
|
||||||
voucher_no: DF.DynamicLink | None
|
voucher_no: DF.DynamicLink | None
|
||||||
|
voucher_subtype: DF.SmallText | None
|
||||||
voucher_type: DF.Link | None
|
voucher_type: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
|
@ -765,7 +765,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
|||||||
reserved_qty = (
|
reserved_qty = (
|
||||||
frappe.qb.from_(p_inv)
|
frappe.qb.from_(p_inv)
|
||||||
.from_(p_item)
|
.from_(p_item)
|
||||||
.select(Sum(p_item.qty).as_("qty"))
|
.select(Sum(p_item.stock_qty).as_("stock_qty"))
|
||||||
.where(
|
.where(
|
||||||
(p_inv.name == p_item.parent)
|
(p_inv.name == p_item.parent)
|
||||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||||
@ -775,7 +775,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
|||||||
)
|
)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
return flt(reserved_qty[0].stock_qty) if reserved_qty else 0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -440,7 +440,7 @@ def reconcile(doc: None | str = None) -> None:
|
|||||||
# Update the parent doc about the exception
|
# Update the parent doc about the exception
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
if traceback:
|
if traceback:
|
||||||
message = "Traceback: <br>" + traceback
|
message = "Traceback: <br>" + traceback
|
||||||
frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message)
|
frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message)
|
||||||
|
@ -1135,11 +1135,17 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
"Asset",
|
||||||
|
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||||
|
fields=["name", "asset_quantity"],
|
||||||
)
|
)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
frappe.db.set_value(
|
||||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
"Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||||
|
)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||||
|
)
|
||||||
|
|
||||||
def make_stock_adjustment_entry(
|
def make_stock_adjustment_entry(
|
||||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||||
|
@ -1985,6 +1985,26 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||||
|
|
||||||
|
def test_debit_note_without_item(self):
|
||||||
|
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
||||||
|
pi.items[0].item_code = ""
|
||||||
|
pi.save()
|
||||||
|
|
||||||
|
self.assertFalse(pi.items[0].item_code)
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
return_pi = make_purchase_invoice(
|
||||||
|
item_name="_Test Item",
|
||||||
|
is_return=1,
|
||||||
|
return_against=pi.name,
|
||||||
|
qty=-10,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
return_pi.items[0].item_code = ""
|
||||||
|
return_pi.save()
|
||||||
|
return_pi.submit()
|
||||||
|
self.assertEqual(return_pi.docstatus, 1)
|
||||||
|
|
||||||
|
|
||||||
def set_advance_flag(company, flag, default_account):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
@ -2121,6 +2141,7 @@ def make_purchase_invoice(**args):
|
|||||||
"items",
|
"items",
|
||||||
{
|
{
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
|
"item_name": args.item_name,
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"qty": args.qty or 5,
|
"qty": args.qty or 5,
|
||||||
"received_qty": args.received_qty or 0,
|
"received_qty": args.received_qty or 0,
|
||||||
|
@ -43,7 +43,7 @@ def start_payment_ledger_repost(docname=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
if traceback:
|
if traceback:
|
||||||
message = "Traceback: <br>" + traceback
|
message = "Traceback: <br>" + traceback
|
||||||
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
|
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
|
||||||
|
@ -586,6 +586,8 @@ class SalesInvoice(SellingController):
|
|||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
def update_status_updater_args(self):
|
def update_status_updater_args(self):
|
||||||
if cint(self.update_stock):
|
if cint(self.update_stock):
|
||||||
self.status_updater.append(
|
self.status_updater.append(
|
||||||
|
@ -1414,10 +1414,11 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
|
|
||||||
def test_serialized_cancel(self):
|
def test_serialized_cancel(self):
|
||||||
si = self.test_serialized()
|
si = self.test_serialized()
|
||||||
si.cancel()
|
si.reload()
|
||||||
|
|
||||||
serial_nos = get_serial_nos_from_bundle(si.get("items")[0].serial_and_batch_bundle)
|
serial_nos = get_serial_nos_from_bundle(si.get("items")[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC"
|
frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC"
|
||||||
)
|
)
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
"warehouse",
|
"warehouse",
|
||||||
"target_warehouse",
|
"target_warehouse",
|
||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
|
"pick_serial_and_batch",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"incoming_rate",
|
"incoming_rate",
|
||||||
@ -897,12 +898,18 @@
|
|||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:parent.update_stock === 1",
|
||||||
|
"fieldname": "pick_serial_and_batch",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Pick Serial / Batch No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-14 18:34:10.479329",
|
"modified": "2023-12-29 13:03:14.121298",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
@ -148,13 +148,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "additional_discount_percentage",
|
"fieldname": "additional_discount_percentage",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Additional DIscount Percentage"
|
"label": "Additional Discount Percentage"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "additional_discount_amount",
|
"fieldname": "additional_discount_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Additional DIscount Amount"
|
"label": "Additional Discount Amount"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -267,7 +267,7 @@
|
|||||||
"link_fieldname": "subscription"
|
"link_fieldname": "subscription"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-09-18 17:48:21.900252",
|
"modified": "2023-12-28 17:20:42.687789",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription",
|
"name": "Subscription",
|
||||||
|
@ -356,18 +356,20 @@ class Subscription(Document):
|
|||||||
self,
|
self,
|
||||||
from_date: Optional[Union[str, datetime.date]] = None,
|
from_date: Optional[Union[str, datetime.date]] = None,
|
||||||
to_date: Optional[Union[str, datetime.date]] = None,
|
to_date: Optional[Union[str, datetime.date]] = None,
|
||||||
|
posting_date: Optional[Union[str, datetime.date]] = None,
|
||||||
) -> Document:
|
) -> Document:
|
||||||
"""
|
"""
|
||||||
Creates a `Invoice` for the `Subscription`, updates `self.invoices` and
|
Creates a `Invoice` for the `Subscription`, updates `self.invoices` and
|
||||||
saves the `Subscription`.
|
saves the `Subscription`.
|
||||||
Backwards compatibility
|
Backwards compatibility
|
||||||
"""
|
"""
|
||||||
return self.create_invoice(from_date=from_date, to_date=to_date)
|
return self.create_invoice(from_date=from_date, to_date=to_date, posting_date=posting_date)
|
||||||
|
|
||||||
def create_invoice(
|
def create_invoice(
|
||||||
self,
|
self,
|
||||||
from_date: Optional[Union[str, datetime.date]] = None,
|
from_date: Optional[Union[str, datetime.date]] = None,
|
||||||
to_date: Optional[Union[str, datetime.date]] = None,
|
to_date: Optional[Union[str, datetime.date]] = None,
|
||||||
|
posting_date: Optional[Union[str, datetime.date]] = None,
|
||||||
) -> Document:
|
) -> Document:
|
||||||
"""
|
"""
|
||||||
Creates a `Invoice`, submits it and returns it
|
Creates a `Invoice`, submits it and returns it
|
||||||
@ -385,11 +387,13 @@ class Subscription(Document):
|
|||||||
invoice = frappe.new_doc(self.invoice_document_type)
|
invoice = frappe.new_doc(self.invoice_document_type)
|
||||||
invoice.company = company
|
invoice.company = company
|
||||||
invoice.set_posting_time = 1
|
invoice.set_posting_time = 1
|
||||||
invoice.posting_date = (
|
|
||||||
self.current_invoice_start
|
if self.generate_invoice_at == "Beginning of the current subscription period":
|
||||||
if self.generate_invoice_at == "Beginning of the current subscription period"
|
invoice.posting_date = self.current_invoice_start
|
||||||
else self.current_invoice_end
|
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||||
)
|
invoice.posting_date = posting_date or self.current_invoice_start
|
||||||
|
else:
|
||||||
|
invoice.posting_date = self.current_invoice_end
|
||||||
|
|
||||||
invoice.cost_center = self.cost_center
|
invoice.cost_center = self.cost_center
|
||||||
|
|
||||||
@ -413,6 +417,7 @@ class Subscription(Document):
|
|||||||
# Subscription is better suited for service items. I won't update `update_stock`
|
# Subscription is better suited for service items. I won't update `update_stock`
|
||||||
# for that reason
|
# for that reason
|
||||||
items_list = self.get_items_from_plans(self.plans, is_prorate())
|
items_list = self.get_items_from_plans(self.plans, is_prorate())
|
||||||
|
|
||||||
for item in items_list:
|
for item in items_list:
|
||||||
item["cost_center"] = self.cost_center
|
item["cost_center"] = self.cost_center
|
||||||
invoice.append("items", item)
|
invoice.append("items", item)
|
||||||
@ -556,7 +561,7 @@ class Subscription(Document):
|
|||||||
if not self.is_current_invoice_generated(
|
if not self.is_current_invoice_generated(
|
||||||
self.current_invoice_start, self.current_invoice_end
|
self.current_invoice_start, self.current_invoice_end
|
||||||
) and self.can_generate_new_invoice(posting_date):
|
) and self.can_generate_new_invoice(posting_date):
|
||||||
self.generate_invoice()
|
self.generate_invoice(posting_date=posting_date)
|
||||||
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
||||||
|
|
||||||
if self.cancel_at_period_end and (
|
if self.cancel_at_period_end and (
|
||||||
|
@ -25,11 +25,26 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
|||||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"group_by",
|
||||||
|
"label": __("Group By"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["Asset Category", "Asset"],
|
||||||
|
"default": "Asset Category",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"asset_category",
|
"fieldname":"asset_category",
|
||||||
"label": __("Asset Category"),
|
"label": __("Asset Category"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Asset Category"
|
"options": "Asset Category",
|
||||||
}
|
"depends_on": "eval: doc.group_by == 'Asset Category'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"asset",
|
||||||
|
"label": __("Asset"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Asset",
|
||||||
|
"depends_on": "eval: doc.group_by == 'Asset'",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,17 @@ def execute(filters=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
|
if filters.get("group_by") == "Asset Category":
|
||||||
|
return get_group_by_asset_category_data(filters)
|
||||||
|
elif filters.get("group_by") == "Asset":
|
||||||
|
return get_group_by_asset_data(filters)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_by_asset_category_data(filters):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
asset_categories = get_asset_categories(filters)
|
asset_categories = get_asset_categories_for_grouped_by_category(filters)
|
||||||
assets = get_assets(filters)
|
assets = get_assets_for_grouped_by_category(filters)
|
||||||
|
|
||||||
for asset_category in asset_categories:
|
for asset_category in asset_categories:
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
@ -38,6 +45,7 @@ def get_data(filters):
|
|||||||
if asset["asset_category"] == asset_category.get("asset_category", "")
|
if asset["asset_category"] == asset_category.get("asset_category", "")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
row.accumulated_depreciation_as_on_to_date = (
|
row.accumulated_depreciation_as_on_to_date = (
|
||||||
flt(row.accumulated_depreciation_as_on_from_date)
|
flt(row.accumulated_depreciation_as_on_from_date)
|
||||||
+ flt(row.depreciation_amount_during_the_period)
|
+ flt(row.depreciation_amount_during_the_period)
|
||||||
@ -57,7 +65,7 @@ def get_data(filters):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_asset_categories(filters):
|
def get_asset_categories_for_grouped_by_category(filters):
|
||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset_category"):
|
if filters.get("asset_category"):
|
||||||
condition += " and asset_category = %(asset_category)s"
|
condition += " and asset_category = %(asset_category)s"
|
||||||
@ -116,7 +124,105 @@ def get_asset_categories(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_assets(filters):
|
def get_asset_details_for_grouped_by_category(filters):
|
||||||
|
condition = ""
|
||||||
|
if filters.get("asset"):
|
||||||
|
condition += " and name = %(asset)s"
|
||||||
|
return frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT name,
|
||||||
|
ifnull(sum(case when purchase_date < %(from_date)s then
|
||||||
|
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_as_on_from_date,
|
||||||
|
ifnull(sum(case when purchase_date >= %(from_date)s then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_new_purchase,
|
||||||
|
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||||
|
and disposal_date >= %(from_date)s
|
||||||
|
and disposal_date <= %(to_date)s then
|
||||||
|
case when status = "Sold" then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_sold_asset,
|
||||||
|
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||||
|
and disposal_date >= %(from_date)s
|
||||||
|
and disposal_date <= %(to_date)s then
|
||||||
|
case when status = "Scrapped" then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_scrapped_asset
|
||||||
|
from `tabAsset`
|
||||||
|
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {}
|
||||||
|
group by name
|
||||||
|
""".format(
|
||||||
|
condition
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"to_date": filters.to_date,
|
||||||
|
"from_date": filters.from_date,
|
||||||
|
"company": filters.company,
|
||||||
|
"asset": filters.get("asset"),
|
||||||
|
},
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_by_asset_data(filters):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
asset_details = get_asset_details_for_grouped_by_category(filters)
|
||||||
|
assets = get_assets_for_grouped_by_asset(filters)
|
||||||
|
|
||||||
|
for asset_detail in asset_details:
|
||||||
|
row = frappe._dict()
|
||||||
|
# row.asset_category = asset_category
|
||||||
|
row.update(asset_detail)
|
||||||
|
|
||||||
|
row.cost_as_on_to_date = (
|
||||||
|
flt(row.cost_as_on_from_date)
|
||||||
|
+ flt(row.cost_of_new_purchase)
|
||||||
|
- flt(row.cost_of_sold_asset)
|
||||||
|
- flt(row.cost_of_scrapped_asset)
|
||||||
|
)
|
||||||
|
|
||||||
|
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||||
|
|
||||||
|
row.accumulated_depreciation_as_on_to_date = (
|
||||||
|
flt(row.accumulated_depreciation_as_on_from_date)
|
||||||
|
+ flt(row.depreciation_amount_during_the_period)
|
||||||
|
- flt(row.depreciation_eliminated_during_the_period)
|
||||||
|
)
|
||||||
|
|
||||||
|
row.net_asset_value_as_on_from_date = flt(row.cost_as_on_from_date) - flt(
|
||||||
|
row.accumulated_depreciation_as_on_from_date
|
||||||
|
)
|
||||||
|
|
||||||
|
row.net_asset_value_as_on_to_date = flt(row.cost_as_on_to_date) - flt(
|
||||||
|
row.accumulated_depreciation_as_on_to_date
|
||||||
|
)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_assets_for_grouped_by_category(filters):
|
||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset_category"):
|
if filters.get("asset_category"):
|
||||||
condition = " and a.asset_category = '{}'".format(filters.get("asset_category"))
|
condition = " and a.asset_category = '{}'".format(filters.get("asset_category"))
|
||||||
@ -178,15 +284,93 @@ def get_assets(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_assets_for_grouped_by_asset(filters):
|
||||||
|
condition = ""
|
||||||
|
if filters.get("asset"):
|
||||||
|
condition = " and a.name = '{}'".format(filters.get("asset"))
|
||||||
|
return frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT results.name as asset,
|
||||||
|
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
|
||||||
|
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||||
|
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
||||||
|
from (SELECT a.name as name,
|
||||||
|
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
||||||
|
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
|
||||||
|
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_amount_during_the_period
|
||||||
|
from `tabGL Entry` gle
|
||||||
|
join `tabAsset` a on
|
||||||
|
gle.against_voucher = a.name
|
||||||
|
join `tabAsset Category Account` aca on
|
||||||
|
aca.parent = a.asset_category and aca.company_name = %(company)s
|
||||||
|
join `tabCompany` company on
|
||||||
|
company.name = %(company)s
|
||||||
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) {0}
|
||||||
|
group by a.name
|
||||||
|
union
|
||||||
|
SELECT a.name as name,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
0 as depreciation_amount_during_the_period
|
||||||
|
from `tabAsset` a
|
||||||
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {0}
|
||||||
|
group by a.name) as results
|
||||||
|
group by results.name
|
||||||
|
""".format(
|
||||||
|
condition
|
||||||
|
),
|
||||||
|
{"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company},
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
return [
|
columns = []
|
||||||
{
|
|
||||||
"label": _("Asset Category"),
|
if filters.get("group_by") == "Asset Category":
|
||||||
"fieldname": "asset_category",
|
columns.append(
|
||||||
"fieldtype": "Link",
|
{
|
||||||
"options": "Asset Category",
|
"label": _("Asset Category"),
|
||||||
"width": 120,
|
"fieldname": "asset_category",
|
||||||
},
|
"fieldtype": "Link",
|
||||||
|
"options": "Asset Category",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif filters.get("group_by") == "Asset":
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": _("Asset"),
|
||||||
|
"fieldname": "asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Asset",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
columns += [
|
||||||
{
|
{
|
||||||
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date),
|
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date),
|
||||||
"fieldname": "cost_as_on_from_date",
|
"fieldname": "cost_as_on_from_date",
|
||||||
@ -254,3 +438,5 @@ def get_columns(filters):
|
|||||||
"width": 200,
|
"width": 200,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
@ -2,7 +2,44 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.query_reports["Budget Variance Report"] = {
|
frappe.query_reports["Budget Variance Report"] = {
|
||||||
"filters": [
|
"filters": get_filters(),
|
||||||
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (column.fieldname.includes(__("variance"))) {
|
||||||
|
|
||||||
|
if (data[column.fieldname] < 0) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
else if (data[column.fieldname] > 0) {
|
||||||
|
value = "<span style='color:green'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function get_filters() {
|
||||||
|
function get_dimensions() {
|
||||||
|
let result = [];
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions",
|
||||||
|
args: {
|
||||||
|
'with_cost_center_and_project': true
|
||||||
|
},
|
||||||
|
async: false,
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc) {
|
||||||
|
result = r.message[0].map(elem => elem.document_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let budget_against_options = get_dimensions();
|
||||||
|
|
||||||
|
let filters = [
|
||||||
{
|
{
|
||||||
fieldname: "from_fiscal_year",
|
fieldname: "from_fiscal_year",
|
||||||
label: __("From Fiscal Year"),
|
label: __("From Fiscal Year"),
|
||||||
@ -44,9 +81,13 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
fieldname: "budget_against",
|
fieldname: "budget_against",
|
||||||
label: __("Budget Against"),
|
label: __("Budget Against"),
|
||||||
fieldtype: "Select",
|
fieldtype: "Select",
|
||||||
options: ["Cost Center", "Project"],
|
options: budget_against_options,
|
||||||
default: "Cost Center",
|
default: "Cost Center",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
|
get_data: function() {
|
||||||
|
console.log(this.options);
|
||||||
|
return ["Emacs", "Rocks"];
|
||||||
|
},
|
||||||
on_change: function() {
|
on_change: function() {
|
||||||
frappe.query_report.set_filter_value("budget_against_filter", []);
|
frappe.query_report.set_filter_value("budget_against_filter", []);
|
||||||
frappe.query_report.refresh();
|
frappe.query_report.refresh();
|
||||||
@ -71,24 +112,8 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
],
|
]
|
||||||
"formatter": function (value, row, column, data, default_formatter) {
|
|
||||||
value = default_formatter(value, row, column, data);
|
|
||||||
|
|
||||||
if (column.fieldname.includes(__("variance"))) {
|
return filters;
|
||||||
|
|
||||||
if (data[column.fieldname] < 0) {
|
|
||||||
value = "<span style='color:red'>" + value + "</span>";
|
|
||||||
}
|
|
||||||
else if (data[column.fieldname] > 0) {
|
|
||||||
value = "<span style='color:green'>" + value + "</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.dimension_filters.forEach((dimension) => {
|
|
||||||
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
|
|
||||||
});
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, qb, scrub
|
||||||
from frappe.utils import getdate, nowdate
|
from frappe.utils import getdate, nowdate
|
||||||
|
|
||||||
|
|
||||||
@ -38,7 +38,6 @@ class PartyLedgerSummaryReport(object):
|
|||||||
"""
|
"""
|
||||||
Additional Columns for 'User Permission' based access control
|
Additional Columns for 'User Permission' based access control
|
||||||
"""
|
"""
|
||||||
from frappe import qb
|
|
||||||
|
|
||||||
if self.filters.party_type == "Customer":
|
if self.filters.party_type == "Customer":
|
||||||
self.territories = frappe._dict({})
|
self.territories = frappe._dict({})
|
||||||
@ -365,13 +364,29 @@ class PartyLedgerSummaryReport(object):
|
|||||||
|
|
||||||
def get_party_adjustment_amounts(self):
|
def get_party_adjustment_amounts(self):
|
||||||
conditions = self.prepare_conditions()
|
conditions = self.prepare_conditions()
|
||||||
income_or_expense = (
|
account_type = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
||||||
"Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
income_or_expense_accounts = frappe.db.get_all(
|
||||||
|
"Account", filters={"account_type": account_type, "company": self.filters.company}, pluck="name"
|
||||||
)
|
)
|
||||||
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
|
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
|
||||||
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
|
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
|
||||||
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
|
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
|
||||||
|
|
||||||
|
gl = qb.DocType("GL Entry")
|
||||||
|
if not income_or_expense_accounts:
|
||||||
|
# prevent empty 'in' condition
|
||||||
|
income_or_expense_accounts.append("")
|
||||||
|
|
||||||
|
accounts_query = (
|
||||||
|
qb.from_(gl)
|
||||||
|
.select(gl.voucher_type, gl.voucher_no)
|
||||||
|
.where(
|
||||||
|
(gl.account.isin(income_or_expense_accounts))
|
||||||
|
& (gl.posting_date.gte(self.filters.from_date))
|
||||||
|
& (gl.posting_date.lte(self.filters.to_date))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
gl_entries = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
@ -381,16 +396,15 @@ class PartyLedgerSummaryReport(object):
|
|||||||
where
|
where
|
||||||
docstatus < 2 and is_cancelled = 0
|
docstatus < 2 and is_cancelled = 0
|
||||||
and (voucher_type, voucher_no) in (
|
and (voucher_type, voucher_no) in (
|
||||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
{accounts_query}
|
||||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
|
||||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2
|
|
||||||
) and (voucher_type, voucher_no) in (
|
) and (voucher_type, voucher_no) in (
|
||||||
select voucher_type, voucher_no from `tabGL Entry` gle
|
select voucher_type, voucher_no from `tabGL Entry` gle
|
||||||
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
|
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
|
||||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
|
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
|
||||||
)
|
)
|
||||||
""".format(
|
""".format(
|
||||||
conditions=conditions, income_or_expense=income_or_expense
|
accounts_query=accounts_query,
|
||||||
|
conditions=conditions,
|
||||||
),
|
),
|
||||||
self.filters,
|
self.filters,
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
@ -414,7 +428,7 @@ class PartyLedgerSummaryReport(object):
|
|||||||
elif gle.party:
|
elif gle.party:
|
||||||
parties.setdefault(gle.party, 0)
|
parties.setdefault(gle.party, 0)
|
||||||
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
|
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
|
||||||
elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense:
|
elif frappe.get_cached_value("Account", gle.account, "account_type") == account_type:
|
||||||
accounts.setdefault(gle.account, 0)
|
accounts.setdefault(gle.account, 0)
|
||||||
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
|
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
|
||||||
else:
|
else:
|
||||||
|
@ -211,7 +211,13 @@ def get_data(
|
|||||||
ignore_accumulated_values_for_fy,
|
ignore_accumulated_values_for_fy,
|
||||||
)
|
)
|
||||||
accumulate_values_into_parents(accounts, accounts_by_name, period_list)
|
accumulate_values_into_parents(accounts, accounts_by_name, period_list)
|
||||||
out = prepare_data(accounts, balance_must_be, period_list, company_currency)
|
out = prepare_data(
|
||||||
|
accounts,
|
||||||
|
balance_must_be,
|
||||||
|
period_list,
|
||||||
|
company_currency,
|
||||||
|
accumulated_values=filters.accumulated_values,
|
||||||
|
)
|
||||||
out = filter_out_zero_value_rows(out, parent_children_map)
|
out = filter_out_zero_value_rows(out, parent_children_map)
|
||||||
|
|
||||||
if out and total:
|
if out and total:
|
||||||
@ -270,7 +276,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name, period_list):
|
|||||||
) + d.get("opening_balance", 0.0)
|
) + d.get("opening_balance", 0.0)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data(accounts, balance_must_be, period_list, company_currency):
|
def prepare_data(accounts, balance_must_be, period_list, company_currency, accumulated_values):
|
||||||
data = []
|
data = []
|
||||||
year_start_date = period_list[0]["year_start_date"].strftime("%Y-%m-%d")
|
year_start_date = period_list[0]["year_start_date"].strftime("%Y-%m-%d")
|
||||||
year_end_date = period_list[-1]["year_end_date"].strftime("%Y-%m-%d")
|
year_end_date = period_list[-1]["year_end_date"].strftime("%Y-%m-%d")
|
||||||
@ -310,8 +316,14 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency):
|
|||||||
has_value = True
|
has_value = True
|
||||||
total += flt(row[period.key])
|
total += flt(row[period.key])
|
||||||
|
|
||||||
row["has_value"] = has_value
|
if accumulated_values:
|
||||||
row["total"] = total
|
# when 'accumulated_values' is enabled, periods have running balance.
|
||||||
|
# so, last period will have the net amount.
|
||||||
|
row["has_value"] = has_value
|
||||||
|
row["total"] = flt(d.get(period_list[-1].key, 0.0), 3)
|
||||||
|
else:
|
||||||
|
row["has_value"] = has_value
|
||||||
|
row["total"] = total
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -52,6 +52,11 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
|
frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"against_voucher_no",
|
||||||
|
"label": __("Against Voucher No"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldtype": "Break",
|
"fieldtype": "Break",
|
||||||
},
|
},
|
||||||
|
@ -200,7 +200,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
name as gl_entry, posting_date, account, party_type, party,
|
name as gl_entry, posting_date, account, party_type, party,
|
||||||
voucher_type, voucher_no, {dimension_fields}
|
voucher_type, voucher_subtype, voucher_no, {dimension_fields}
|
||||||
cost_center, project, {transaction_currency_fields}
|
cost_center, project, {transaction_currency_fields}
|
||||||
against_voucher_type, against_voucher, account_currency,
|
against_voucher_type, against_voucher, account_currency,
|
||||||
against, is_opening, creation {select_fields}
|
against, is_opening, creation {select_fields}
|
||||||
@ -238,6 +238,9 @@ def get_conditions(filters):
|
|||||||
if filters.get("voucher_no"):
|
if filters.get("voucher_no"):
|
||||||
conditions.append("voucher_no=%(voucher_no)s")
|
conditions.append("voucher_no=%(voucher_no)s")
|
||||||
|
|
||||||
|
if filters.get("against_voucher_no"):
|
||||||
|
conditions.append("against_voucher=%(against_voucher_no)s")
|
||||||
|
|
||||||
if filters.get("voucher_no_not_in"):
|
if filters.get("voucher_no_not_in"):
|
||||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||||
|
|
||||||
@ -608,6 +611,12 @@ def get_columns(filters):
|
|||||||
|
|
||||||
columns += [
|
columns += [
|
||||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
|
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
|
||||||
|
{
|
||||||
|
"label": _("Voucher Subtype"),
|
||||||
|
"fieldname": "voucher_subtype",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 180,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": _("Voucher No"),
|
"label": _("Voucher No"),
|
||||||
"fieldname": "voucher_no",
|
"fieldname": "voucher_no",
|
||||||
|
@ -82,14 +82,25 @@ def get_report_summary(
|
|||||||
if filters.get("accumulated_in_group_company"):
|
if filters.get("accumulated_in_group_company"):
|
||||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||||
|
|
||||||
for period in period_list:
|
if filters.accumulated_values:
|
||||||
key = period if consolidated else period.key
|
# when 'accumulated_values' is enabled, periods have running balance.
|
||||||
|
# so, last period will have the net amount.
|
||||||
|
key = period_list[-1].key
|
||||||
if income:
|
if income:
|
||||||
net_income += income[-2].get(key)
|
net_income = income[-2].get(key)
|
||||||
if expense:
|
if expense:
|
||||||
net_expense += expense[-2].get(key)
|
net_expense = expense[-2].get(key)
|
||||||
if net_profit_loss:
|
if net_profit_loss:
|
||||||
net_profit += net_profit_loss.get(key)
|
net_profit = net_profit_loss.get(key)
|
||||||
|
else:
|
||||||
|
for period in period_list:
|
||||||
|
key = period if consolidated else period.key
|
||||||
|
if income:
|
||||||
|
net_income += income[-2].get(key)
|
||||||
|
if expense:
|
||||||
|
net_expense += expense[-2].get(key)
|
||||||
|
if net_profit_loss:
|
||||||
|
net_profit += net_profit_loss.get(key)
|
||||||
|
|
||||||
if len(period_list) == 1 and periodicity == "Yearly":
|
if len(period_list) == 1 and periodicity == "Yearly":
|
||||||
profit_label = _("Profit This Year")
|
profit_label = _("Profit This Year")
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import add_days, getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import execute
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestProfitAndLossStatement(AccountsTestMixin, FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.create_customer()
|
||||||
|
self.create_item()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def create_sales_invoice(self, qty=1, rate=150, no_payment_schedule=False, do_not_submit=False):
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=self.item,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.customer,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
posting_date=today(),
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
rate=rate,
|
||||||
|
price_list_rate=rate,
|
||||||
|
qty=qty,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
si = si.save()
|
||||||
|
if not do_not_submit:
|
||||||
|
si = si.submit()
|
||||||
|
return si
|
||||||
|
|
||||||
|
def get_fiscal_year(self):
|
||||||
|
active_fy = frappe.db.get_all(
|
||||||
|
"Fiscal Year",
|
||||||
|
filters={"disabled": 0, "year_start_date": ("<=", today()), "year_end_date": (">=", today())},
|
||||||
|
)[0]
|
||||||
|
return frappe.get_doc("Fiscal Year", active_fy.name)
|
||||||
|
|
||||||
|
def get_report_filters(self):
|
||||||
|
fy = self.get_fiscal_year()
|
||||||
|
return frappe._dict(
|
||||||
|
company=self.company,
|
||||||
|
from_fiscal_year=fy.name,
|
||||||
|
to_fiscal_year=fy.name,
|
||||||
|
period_start_date=fy.year_start_date,
|
||||||
|
period_end_date=fy.year_end_date,
|
||||||
|
filter_based_on="Fiscal Year",
|
||||||
|
periodicity="Monthly",
|
||||||
|
accumulated_vallues=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_profit_and_loss_output_and_summary(self):
|
||||||
|
si = self.create_sales_invoice(qty=1, rate=150)
|
||||||
|
|
||||||
|
filters = self.get_report_filters()
|
||||||
|
period_list = get_period_list(
|
||||||
|
filters.from_fiscal_year,
|
||||||
|
filters.to_fiscal_year,
|
||||||
|
filters.period_start_date,
|
||||||
|
filters.period_end_date,
|
||||||
|
filters.filter_based_on,
|
||||||
|
filters.periodicity,
|
||||||
|
company=filters.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = execute(filters)[1]
|
||||||
|
current_period = [x for x in period_list if x.from_date <= getdate() and x.to_date >= getdate()][
|
||||||
|
0
|
||||||
|
]
|
||||||
|
current_period_key = current_period.key
|
||||||
|
without_current_period = [x for x in period_list if x.key != current_period.key]
|
||||||
|
# all period except current period(whence invoice was posted), should be '0'
|
||||||
|
for acc in result:
|
||||||
|
if acc:
|
||||||
|
with self.subTest(acc=acc):
|
||||||
|
for period in without_current_period:
|
||||||
|
self.assertEqual(acc[period.key], 0)
|
||||||
|
|
||||||
|
for acc in result:
|
||||||
|
if acc:
|
||||||
|
with self.subTest(current_period_key=current_period_key):
|
||||||
|
self.assertEqual(acc[current_period_key], 150)
|
||||||
|
self.assertEqual(acc["total"], 150)
|
@ -117,8 +117,3 @@ frappe.query_reports["Profitability Analysis"] = {
|
|||||||
"parent_field": "parent_account",
|
"parent_field": "parent_account",
|
||||||
"initial_depth": 3
|
"initial_depth": 3
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.dimension_filters.forEach((dimension) => {
|
|
||||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
@ -225,6 +225,11 @@ class AccountsController(TransactionBase):
|
|||||||
apply_pricing_rule_on_transaction(self)
|
apply_pricing_rule_on_transaction(self)
|
||||||
|
|
||||||
self.set_total_in_words()
|
self.set_total_in_words()
|
||||||
|
self.set_default_letter_head()
|
||||||
|
|
||||||
|
def set_default_letter_head(self):
|
||||||
|
if hasattr(self, "letter_head") and not self.letter_head:
|
||||||
|
self.letter_head = frappe.db.get_value("Company", self.company, "default_letter_head")
|
||||||
|
|
||||||
def init_internal_values(self):
|
def init_internal_values(self):
|
||||||
# init all the internal values as 0 on sa
|
# init all the internal values as 0 on sa
|
||||||
@ -874,6 +879,7 @@ class AccountsController(TransactionBase):
|
|||||||
"project": self.get("project"),
|
"project": self.get("project"),
|
||||||
"post_net_value": args.get("post_net_value"),
|
"post_net_value": args.get("post_net_value"),
|
||||||
"voucher_detail_no": args.get("voucher_detail_no"),
|
"voucher_detail_no": args.get("voucher_detail_no"),
|
||||||
|
"voucher_subtype": self.get_voucher_subtype(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -927,8 +933,33 @@ class AccountsController(TransactionBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
|
||||||
|
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
|
||||||
|
|
||||||
|
if not args.get("against_voucher") and self.get("against_voucher"):
|
||||||
|
gl_dict.update({"against_voucher": self.get("against_voucher")})
|
||||||
|
|
||||||
return gl_dict
|
return gl_dict
|
||||||
|
|
||||||
|
def get_voucher_subtype(self):
|
||||||
|
voucher_subtypes = {
|
||||||
|
"Journal Entry": "voucher_type",
|
||||||
|
"Payment Entry": "payment_type",
|
||||||
|
"Stock Entry": "stock_entry_type",
|
||||||
|
"Asset Capitalization": "entry_type",
|
||||||
|
}
|
||||||
|
if self.doctype in voucher_subtypes:
|
||||||
|
return self.get(voucher_subtypes[self.doctype])
|
||||||
|
elif self.doctype == "Purchase Receipt" and self.is_return:
|
||||||
|
return "Purchase Return"
|
||||||
|
elif self.doctype == "Delivery Note" and self.is_return:
|
||||||
|
return "Sales Return"
|
||||||
|
elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
|
||||||
|
return "Credit Note"
|
||||||
|
elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
|
||||||
|
return "Debit Note"
|
||||||
|
return self.doctype
|
||||||
|
|
||||||
def get_value_in_transaction_currency(self, account_currency, args, field):
|
def get_value_in_transaction_currency(self, account_currency, args, field):
|
||||||
if account_currency == self.get("currency"):
|
if account_currency == self.get("currency"):
|
||||||
return args.get(field + "_in_account_currency")
|
return args.get(field + "_in_account_currency")
|
||||||
@ -2393,6 +2424,7 @@ def validate_taxes_and_charges(tax):
|
|||||||
|
|
||||||
def validate_account_head(idx, account, company, context=""):
|
def validate_account_head(idx, account, company, context=""):
|
||||||
account_company = frappe.get_cached_value("Account", account, "company")
|
account_company = frappe.get_cached_value("Account", account, "company")
|
||||||
|
is_group = frappe.get_cached_value("Account", account, "is_group")
|
||||||
|
|
||||||
if account_company != company:
|
if account_company != company:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@ -2402,6 +2434,12 @@ def validate_account_head(idx, account, company, context=""):
|
|||||||
title=_("Invalid Account"),
|
title=_("Invalid Account"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if is_group:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row {0}: Account {1} is a Group Account").format(idx, frappe.bold(account)),
|
||||||
|
title=_("Invalid Account"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_cost_center(tax, doc):
|
def validate_cost_center(tax, doc):
|
||||||
if not tax.cost_center:
|
if not tax.cost_center:
|
||||||
|
@ -562,16 +562,17 @@ def make_return_doc(
|
|||||||
if default_warehouse_for_sales_return:
|
if default_warehouse_for_sales_return:
|
||||||
target_doc.warehouse = default_warehouse_for_sales_return
|
target_doc.warehouse = default_warehouse_for_sales_return
|
||||||
|
|
||||||
item_details = frappe.get_cached_value(
|
if source_doc.item_code:
|
||||||
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
item_details = frappe.get_cached_value(
|
||||||
)
|
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
if not item_details.has_batch_no and not item_details.has_serial_no:
|
if not item_details.has_batch_no and not item_details.has_serial_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
for qty_field in ["stock_qty", "rejected_qty"]:
|
for qty_field in ["stock_qty", "rejected_qty"]:
|
||||||
if target_doc.get(qty_field):
|
if target_doc.get(qty_field):
|
||||||
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
|
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
|
||||||
|
|
||||||
def update_terms(source_doc, target_doc, source_parent):
|
def update_terms(source_doc, target_doc, source_parent):
|
||||||
target_doc.payment_amount = -source_doc.payment_amount
|
target_doc.payment_amount = -source_doc.payment_amount
|
||||||
|
@ -37,6 +37,7 @@ welcome_email = "erpnext.setup.utils.welcome_email"
|
|||||||
# setup wizard
|
# setup wizard
|
||||||
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
|
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
|
||||||
setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages"
|
setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages"
|
||||||
|
setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_demo"
|
||||||
setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
|
setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
|
||||||
|
|
||||||
before_install = [
|
before_install = [
|
||||||
|
@ -1486,3 +1486,47 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def get_op_cost_from_sub_assemblies(bom_no, op_cost=0):
|
||||||
|
# Get operating cost from sub-assemblies
|
||||||
|
|
||||||
|
bom_items = frappe.get_all(
|
||||||
|
"BOM Item", filters={"parent": bom_no, "docstatus": 1}, fields=["bom_no"], order_by="idx asc"
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in bom_items:
|
||||||
|
if not row.bom_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cost := frappe.get_cached_value("BOM", row.bom_no, "operating_cost_per_bom_quantity"):
|
||||||
|
op_cost += flt(cost)
|
||||||
|
get_op_cost_from_sub_assemblies(row.bom_no, op_cost)
|
||||||
|
|
||||||
|
return op_cost
|
||||||
|
|
||||||
|
|
||||||
|
def get_scrap_items_from_sub_assemblies(bom_no, company, qty, scrap_items=None):
|
||||||
|
if not scrap_items:
|
||||||
|
scrap_items = {}
|
||||||
|
|
||||||
|
bom_items = frappe.get_all(
|
||||||
|
"BOM Item",
|
||||||
|
filters={"parent": bom_no, "docstatus": 1},
|
||||||
|
fields=["bom_no", "qty"],
|
||||||
|
order_by="idx asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in bom_items:
|
||||||
|
if not row.bom_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
qty = flt(row.qty) * flt(qty)
|
||||||
|
items = get_bom_items_as_dict(
|
||||||
|
row.bom_no, company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||||
|
)
|
||||||
|
scrap_items.update(items)
|
||||||
|
|
||||||
|
get_scrap_items_from_sub_assemblies(row.bom_no, company, qty, scrap_items)
|
||||||
|
|
||||||
|
return scrap_items
|
||||||
|
@ -251,7 +251,7 @@ class BOMCreator(Document):
|
|||||||
|
|
||||||
frappe.msgprint(_("BOMs created successfully"))
|
frappe.msgprint(_("BOMs created successfully"))
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
self.db_set(
|
self.db_set(
|
||||||
{
|
{
|
||||||
"status": "Failed",
|
"status": "Failed",
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
"oldfieldname": "item_code",
|
"oldfieldname": "item_code",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
@ -170,7 +171,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-14 18:35:40.856895",
|
"modified": "2024-01-02 13:49:36.211586",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Explosion Item",
|
"name": "BOM Explosion Item",
|
||||||
|
@ -273,35 +273,39 @@ class JobCard(Document):
|
|||||||
|
|
||||||
def has_overlap(self, production_capacity, time_logs):
|
def has_overlap(self, production_capacity, time_logs):
|
||||||
overlap = False
|
overlap = False
|
||||||
if production_capacity == 1 and len(time_logs) > 0:
|
if production_capacity == 1 and len(time_logs) >= 1:
|
||||||
return True
|
return True
|
||||||
|
if not len(time_logs):
|
||||||
|
return False
|
||||||
|
|
||||||
# Check overlap exists or not between the overlapping time logs with the current Job Card
|
# sorting overlapping job cards as per from_time
|
||||||
for row in time_logs:
|
time_logs = sorted(time_logs, key=lambda x: x.get("from_time"))
|
||||||
count = 1
|
# alloted_capacity has key number starting from 1. Key number will increment by 1 if non sequential job card found
|
||||||
for next_row in time_logs:
|
# if key number reaches/crosses to production_capacity means capacity is full and overlap error generated
|
||||||
if row.name == next_row.name:
|
# this will store last to_time of sequential job cards
|
||||||
continue
|
alloted_capacity = {1: time_logs[0]["to_time"]}
|
||||||
|
# flag for sequential Job card found
|
||||||
if (
|
sequential_job_card_found = False
|
||||||
(
|
for i in range(1, len(time_logs)):
|
||||||
get_datetime(next_row.from_time) >= get_datetime(row.from_time)
|
# scanning for all Existing keys
|
||||||
and get_datetime(next_row.from_time) <= get_datetime(row.to_time)
|
for key in alloted_capacity.keys():
|
||||||
)
|
# if current Job Card from time is greater than last to_time in that key means these job card are sequential
|
||||||
or (
|
if alloted_capacity[key] <= time_logs[i]["from_time"]:
|
||||||
get_datetime(next_row.to_time) >= get_datetime(row.from_time)
|
# So update key's value with last to_time
|
||||||
and get_datetime(next_row.to_time) <= get_datetime(row.to_time)
|
alloted_capacity[key] = time_logs[i]["to_time"]
|
||||||
)
|
# flag is true as we get sequential Job Card for that key
|
||||||
or (
|
sequential_job_card_found = True
|
||||||
get_datetime(next_row.from_time) <= get_datetime(row.from_time)
|
# Immediately break so that job card to time is not added with any other key except this
|
||||||
and get_datetime(next_row.to_time) >= get_datetime(row.to_time)
|
break
|
||||||
)
|
# if sequential job card not found above means it is overlapping so increment key number to alloted_capacity
|
||||||
):
|
if not sequential_job_card_found:
|
||||||
count += 1
|
# increment key number
|
||||||
|
key = key + 1
|
||||||
if count > production_capacity:
|
# for that key last to time is assigned.
|
||||||
return True
|
alloted_capacity[key] = time_logs[i]["to_time"]
|
||||||
|
if len(alloted_capacity) >= production_capacity:
|
||||||
|
# if number of keys greater or equal to production caoacity means full capacity is utilized and we should throw overlap error
|
||||||
|
return True
|
||||||
return overlap
|
return overlap
|
||||||
|
|
||||||
def get_time_logs(self, args, doctype, check_next_available_slot=False):
|
def get_time_logs(self, args, doctype, check_next_available_slot=False):
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"job_card_excess_transfer",
|
"job_card_excess_transfer",
|
||||||
"other_settings_section",
|
"other_settings_section",
|
||||||
"update_bom_costs_automatically",
|
"update_bom_costs_automatically",
|
||||||
|
"set_op_cost_and_scrape_from_sub_assemblies",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"make_serial_no_batch_from_work_order"
|
"make_serial_no_batch_from_work_order"
|
||||||
],
|
],
|
||||||
@ -194,13 +195,20 @@
|
|||||||
"fieldname": "job_card_excess_transfer",
|
"fieldname": "job_card_excess_transfer",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Excess Material Transfer"
|
"label": "Allow Excess Material Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "In the case of 'Use Multi-Level BOM' in a work order, if the user wishes to add sub-assembly costs to Finished Goods items without using a job card as well the scrap items, then this option needs to be enable.",
|
||||||
|
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-13 22:09:09.401559",
|
"modified": "2023-12-28 16:37:44.874096",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Manufacturing Settings",
|
"name": "Manufacturing Settings",
|
||||||
@ -216,5 +224,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -32,6 +32,7 @@ class ManufacturingSettings(Document):
|
|||||||
mins_between_operations: DF.Int
|
mins_between_operations: DF.Int
|
||||||
overproduction_percentage_for_sales_order: DF.Percent
|
overproduction_percentage_for_sales_order: DF.Percent
|
||||||
overproduction_percentage_for_work_order: DF.Percent
|
overproduction_percentage_for_work_order: DF.Percent
|
||||||
|
set_op_cost_and_scrape_from_sub_assemblies: DF.Check
|
||||||
update_bom_costs_automatically: DF.Check
|
update_bom_costs_automatically: DF.Check
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
|
@ -1602,6 +1602,10 @@ def make_bom(**args):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if args.operating_cost_per_bom_quantity:
|
||||||
|
bom.fg_based_operating_cost = 1
|
||||||
|
bom.operating_cost_per_bom_quantity = args.operating_cost_per_bom_quantity
|
||||||
|
|
||||||
for item in args.raw_materials:
|
for item in args.raw_materials:
|
||||||
item_doc = frappe.get_doc("Item", item)
|
item_doc = frappe.get_doc("Item", item)
|
||||||
bom.append(
|
bom.append(
|
||||||
|
@ -920,11 +920,9 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
"Test RM Item 2 for Scrap Item Test",
|
"Test RM Item 2 for Scrap Item Test",
|
||||||
]
|
]
|
||||||
|
|
||||||
from_time = add_days(now(), -1)
|
|
||||||
job_cards = frappe.get_all(
|
job_cards = frappe.get_all(
|
||||||
"Job Card Time Log",
|
"Job Card Time Log",
|
||||||
fields=["distinct parent as name", "docstatus"],
|
fields=["distinct parent as name", "docstatus"],
|
||||||
filters={"from_time": (">", from_time)},
|
|
||||||
order_by="creation asc",
|
order_by="creation asc",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1731,6 +1729,93 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
job_card2.time_logs = []
|
job_card2.time_logs = []
|
||||||
job_card2.save()
|
job_card2.save()
|
||||||
|
|
||||||
|
def test_op_cost_and_scrap_based_on_sub_assemblies(self):
|
||||||
|
# Make Sub Assembly BOM 1
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 1
|
||||||
|
)
|
||||||
|
|
||||||
|
items = {
|
||||||
|
"Test Final FG Item": 0,
|
||||||
|
"Test Final SF Item 1": 0,
|
||||||
|
"Test Final SF Item 2": 0,
|
||||||
|
"Test Final RM Item 1": 100,
|
||||||
|
"Test Final RM Item 2": 200,
|
||||||
|
"Test Final Scrap Item 1": 50,
|
||||||
|
"Test Final Scrap Item 2": 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if not frappe.db.exists("Item", item):
|
||||||
|
item_properties = {"is_stock_item": 1, "valuation_rate": items[item]}
|
||||||
|
|
||||||
|
make_item(item_code=item, properties=item_properties),
|
||||||
|
|
||||||
|
prepare_boms_for_sub_assembly_test()
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(
|
||||||
|
production_item="Test Final FG Item",
|
||||||
|
qty=10,
|
||||||
|
use_multi_level_bom=1,
|
||||||
|
skip_transfer=1,
|
||||||
|
from_wip_warehouse=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
se_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||||
|
se_doc.save()
|
||||||
|
|
||||||
|
self.assertTrue(se_doc.additional_costs)
|
||||||
|
scrap_items = []
|
||||||
|
for item in se_doc.items:
|
||||||
|
if item.is_scrap_item:
|
||||||
|
scrap_items.append(item.item_code)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(scrap_items), sorted(["Test Final Scrap Item 1", "Test Final Scrap Item 2"])
|
||||||
|
)
|
||||||
|
for row in se_doc.additional_costs:
|
||||||
|
self.assertEqual(row.amount, 3000)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_boms_for_sub_assembly_test():
|
||||||
|
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}):
|
||||||
|
bom = make_bom(
|
||||||
|
item="Test Final SF Item 1",
|
||||||
|
source_warehouse="Stores - _TC",
|
||||||
|
raw_materials=["Test Final RM Item 1"],
|
||||||
|
operating_cost_per_bom_quantity=100,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 1", "qty": 1})
|
||||||
|
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 2"}):
|
||||||
|
bom = make_bom(
|
||||||
|
item="Test Final SF Item 2",
|
||||||
|
source_warehouse="Stores - _TC",
|
||||||
|
raw_materials=["Test Final RM Item 2"],
|
||||||
|
operating_cost_per_bom_quantity=200,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 2", "qty": 1})
|
||||||
|
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
if not frappe.db.exists("BOM", {"item": "Test Final FG Item"}):
|
||||||
|
bom = make_bom(
|
||||||
|
item="Test Final FG Item",
|
||||||
|
source_warehouse="Stores - _TC",
|
||||||
|
raw_materials=["Test Final SF Item 1", "Test Final SF Item 2"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_workstation_type_check():
|
def prepare_data_for_workstation_type_check():
|
||||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||||
@ -1977,6 +2062,7 @@ def make_wo_order_test_record(**args):
|
|||||||
wo_order.sales_order = args.sales_order or None
|
wo_order.sales_order = args.sales_order or None
|
||||||
wo_order.planned_start_date = args.planned_start_date or now()
|
wo_order.planned_start_date = args.planned_start_date or now()
|
||||||
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
||||||
|
wo_order.from_wip_warehouse = args.from_wip_warehouse or 0
|
||||||
|
|
||||||
if args.source_warehouse:
|
if args.source_warehouse:
|
||||||
for item in wo_order.get("required_items"):
|
for item in wo_order.get("required_items"):
|
||||||
|
@ -843,7 +843,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
freeze_message: __("Mapping {0} ...", [opts.source_doctype]),
|
freeze_message: __("Mapping {0} ...", [opts.source_doctype]),
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
var doc = frappe.model.sync(r.message);
|
frappe.model.sync(r.message);
|
||||||
cur_frm.dirty();
|
cur_frm.dirty();
|
||||||
cur_frm.refresh();
|
cur_frm.refresh();
|
||||||
}
|
}
|
||||||
@ -870,6 +870,11 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
target: opts.target,
|
target: opts.target,
|
||||||
date_field: opts.date_field || undefined,
|
date_field: opts.date_field || undefined,
|
||||||
setters: opts.setters,
|
setters: opts.setters,
|
||||||
|
data_fields: [{
|
||||||
|
fieldname: 'merge_taxes',
|
||||||
|
fieldtype: 'Check',
|
||||||
|
label: __('Merge taxes from multiple documents'),
|
||||||
|
}],
|
||||||
get_query: opts.get_query,
|
get_query: opts.get_query,
|
||||||
add_filters_group: 1,
|
add_filters_group: 1,
|
||||||
allow_child_item_selection: opts.allow_child_item_selection,
|
allow_child_item_selection: opts.allow_child_item_selection,
|
||||||
@ -883,10 +888,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
opts.source_name = values;
|
opts.source_name = values;
|
||||||
if (opts.allow_child_item_selection) {
|
opts.args = args;
|
||||||
// args contains filtered child docnames
|
|
||||||
opts.args = args;
|
|
||||||
}
|
|
||||||
d.dialog.hide();
|
d.dialog.hide();
|
||||||
_map();
|
_map();
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,7 @@ def update_itemised_tax_data(doc):
|
|||||||
# dont even bother checking in item tax template as it contains both input and output accounts - double the tax rate
|
# dont even bother checking in item tax template as it contains both input and output accounts - double the tax rate
|
||||||
item_code = row.item_code or row.item_name
|
item_code = row.item_code or row.item_name
|
||||||
if itemised_tax.get(item_code):
|
if itemised_tax.get(item_code):
|
||||||
for tax in itemised_tax.get(row.item_code).values():
|
for tax in itemised_tax.get(item_code).values():
|
||||||
_tax_rate = flt(tax.get("tax_rate", 0), row.precision("tax_rate"))
|
_tax_rate = flt(tax.get("tax_rate", 0), row.precision("tax_rate"))
|
||||||
tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount"))
|
tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount"))
|
||||||
tax_rate += _tax_rate
|
tax_rate += _tax_rate
|
||||||
|
@ -448,7 +448,6 @@
|
|||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
|
||||||
"fieldname": "credit_limits",
|
"fieldname": "credit_limits",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Credit Limit",
|
"label": "Credit Limit",
|
||||||
@ -584,7 +583,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-10-19 16:56:27.327035",
|
"modified": "2023-12-28 13:15:36.298369",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
|
@ -37,11 +37,6 @@ def get_setup_stages(args=None):
|
|||||||
{"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")},
|
{"fn": setup_defaults, "args": args, "fail_msg": _("Failed to setup defaults")},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"status": _("Setting up demo data"),
|
|
||||||
"fail_msg": _("Failed to setup demo data"),
|
|
||||||
"tasks": [{"fn": setup_demo, "args": args, "fail_msg": _("Failed to setup demo data")}],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"status": _("Wrapping up"),
|
"status": _("Wrapping up"),
|
||||||
"fail_msg": _("Failed to login"),
|
"fail_msg": _("Failed to login"),
|
||||||
|
@ -149,6 +149,4 @@ def prepare_closing_stock_balance(name):
|
|||||||
doc.db_set("status", "Completed")
|
doc.db_set("status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
doc.db_set("status", "Failed")
|
doc.db_set("status", "Failed")
|
||||||
traceback = frappe.get_traceback()
|
doc.log_error(title="Closing Stock Balance Failed")
|
||||||
|
|
||||||
frappe.log_error("Closing Stock Balance Failed", traceback, doc.doctype, doc.name)
|
|
||||||
|
@ -431,6 +431,8 @@ class DeliveryNote(SellingController):
|
|||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
def update_stock_reservation_entries(self) -> None:
|
def update_stock_reservation_entries(self) -> None:
|
||||||
"""Updates Delivered Qty in Stock Reservation Entries."""
|
"""Updates Delivered Qty in Stock Reservation Entries."""
|
||||||
|
|
||||||
|
@ -1478,6 +1478,46 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
returned_dn.reload()
|
returned_dn.reload()
|
||||||
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
|
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
|
||||||
|
|
||||||
|
def test_batch_with_non_stock_uom(self):
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1
|
||||||
|
)
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TESTBATCH.#####",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if not frappe.db.exists("UOM Conversion Detail", {"parent": item.name, "uom": "Kg"}):
|
||||||
|
item.append("uoms", {"uom": "Kg", "conversion_factor": 5.0})
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
item_code = item.name
|
||||||
|
|
||||||
|
make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100.0)
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code, qty=1, rate=500, warehouse="_Test Warehouse - _TC", do_not_save=True
|
||||||
|
)
|
||||||
|
dn.items[0].uom = "Kg"
|
||||||
|
dn.items[0].conversion_factor = 5.0
|
||||||
|
|
||||||
|
dn.save()
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
self.assertEqual(dn.items[0].stock_qty, 5.0)
|
||||||
|
voucher_detail_no = dn.items[0].name
|
||||||
|
delivered_batch_qty = frappe.db.get_value(
|
||||||
|
"Serial and Batch Bundle", {"voucher_detail_no": voucher_detail_no}, "total_qty"
|
||||||
|
)
|
||||||
|
self.assertEqual(abs(delivered_batch_qty), 5.0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
|
@ -1121,8 +1121,39 @@ def get_item_wise_returned_qty(pr_doc):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def merge_taxes(source_taxes, target_doc):
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||||
|
update_item_wise_tax_detail,
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_taxes = target_doc.get("taxes") or []
|
||||||
|
idx = 1
|
||||||
|
for tax in source_taxes:
|
||||||
|
found = False
|
||||||
|
for t in existing_taxes:
|
||||||
|
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||||
|
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||||
|
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||||
|
update_item_wise_tax_detail(t, tax)
|
||||||
|
found = True
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
tax.charge_type = "Actual"
|
||||||
|
tax.idx = idx
|
||||||
|
idx += 1
|
||||||
|
tax.included_in_print_rate = 0
|
||||||
|
tax.dont_recompute_tax = 1
|
||||||
|
tax.row_id = ""
|
||||||
|
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||||
|
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||||
|
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||||
|
existing_taxes.append(tax)
|
||||||
|
|
||||||
|
target_doc.set("taxes", existing_taxes)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_purchase_invoice(source_name, target_doc=None):
|
def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||||
from erpnext.accounts.party import get_payment_terms_template
|
from erpnext.accounts.party import get_payment_terms_template
|
||||||
|
|
||||||
doc = frappe.get_doc("Purchase Receipt", source_name)
|
doc = frappe.get_doc("Purchase Receipt", source_name)
|
||||||
@ -1139,6 +1170,10 @@ def make_purchase_invoice(source_name, target_doc=None):
|
|||||||
)
|
)
|
||||||
doc.run_method("onload")
|
doc.run_method("onload")
|
||||||
doc.run_method("set_missing_values")
|
doc.run_method("set_missing_values")
|
||||||
|
|
||||||
|
if args and args.get("merge_taxes"):
|
||||||
|
merge_taxes(source.get("taxes") or [], doc)
|
||||||
|
|
||||||
doc.run_method("calculate_taxes_and_totals")
|
doc.run_method("calculate_taxes_and_totals")
|
||||||
doc.set_payment_schedule()
|
doc.set_payment_schedule()
|
||||||
|
|
||||||
@ -1202,7 +1237,11 @@ def make_purchase_invoice(source_name, target_doc=None):
|
|||||||
if not doc.get("is_return")
|
if not doc.get("is_return")
|
||||||
else get_pending_qty(d)[0] > 0,
|
else get_pending_qty(d)[0] > 0,
|
||||||
},
|
},
|
||||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
|
"Purchase Taxes and Charges": {
|
||||||
|
"doctype": "Purchase Taxes and Charges",
|
||||||
|
"add_if_empty": True,
|
||||||
|
"ignore": args.get("merge_taxes") if args else 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
target_doc,
|
target_doc,
|
||||||
set_missing_values,
|
set_missing_values,
|
||||||
|
@ -294,7 +294,7 @@ def repost(doc):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
doc.log_error("Unable to repost item valuation")
|
doc.log_error("Unable to repost item valuation")
|
||||||
|
|
||||||
message = frappe.message_log.pop() if frappe.message_log else ""
|
message = frappe.message_log.pop() if frappe.message_log else ""
|
||||||
|
@ -85,6 +85,7 @@ class SerialandBatchBundle(Document):
|
|||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.reset_serial_batch_bundle()
|
||||||
self.set_batch_no()
|
self.set_batch_no()
|
||||||
self.validate_serial_and_batch_no()
|
self.validate_serial_and_batch_no()
|
||||||
self.validate_duplicate_serial_and_batch_no()
|
self.validate_duplicate_serial_and_batch_no()
|
||||||
@ -100,6 +101,15 @@ class SerialandBatchBundle(Document):
|
|||||||
self.set_incoming_rate()
|
self.set_incoming_rate()
|
||||||
self.calculate_qty_and_amount()
|
self.calculate_qty_and_amount()
|
||||||
|
|
||||||
|
def reset_serial_batch_bundle(self):
|
||||||
|
if self.is_new() and self.amended_from:
|
||||||
|
for field in ["is_cancelled", "is_rejected"]:
|
||||||
|
if self.get(field):
|
||||||
|
self.set(field, 0)
|
||||||
|
|
||||||
|
if self.voucher_detail_no:
|
||||||
|
self.voucher_detail_no = None
|
||||||
|
|
||||||
def set_batch_no(self):
|
def set_batch_no(self):
|
||||||
if self.has_serial_no and self.has_batch_no:
|
if self.has_serial_no and self.has_batch_no:
|
||||||
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
@ -483,7 +493,11 @@ class SerialandBatchBundle(Document):
|
|||||||
if row.get("doctype") in ["Subcontracting Receipt Supplied Item"]:
|
if row.get("doctype") in ["Subcontracting Receipt Supplied Item"]:
|
||||||
qty_field = "consumed_qty"
|
qty_field = "consumed_qty"
|
||||||
|
|
||||||
if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
|
qty = row.get(qty_field)
|
||||||
|
if qty_field == "qty" and row.get("stock_qty"):
|
||||||
|
qty = row.get("stock_qty")
|
||||||
|
|
||||||
|
if abs(abs(flt(self.total_qty, precision)) - abs(flt(qty, precision))) > 0.01:
|
||||||
self.throw_error_message(
|
self.throw_error_message(
|
||||||
f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
|
f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
|
||||||
)
|
)
|
||||||
@ -910,7 +924,11 @@ def upload_csv_file(item_code, file_path):
|
|||||||
|
|
||||||
|
|
||||||
def get_serial_batch_from_csv(item_code, file_path):
|
def get_serial_batch_from_csv(item_code, file_path):
|
||||||
file_path = frappe.get_site_path() + file_path
|
if "private" in file_path:
|
||||||
|
file_path = frappe.get_site_path() + file_path
|
||||||
|
else:
|
||||||
|
file_path = frappe.get_site_path() + "/public" + file_path
|
||||||
|
|
||||||
serial_nos = []
|
serial_nos = []
|
||||||
batch_nos = []
|
batch_nos = []
|
||||||
|
|
||||||
|
@ -427,11 +427,12 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
|||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|
||||||
item = make_item(
|
item = make_item(
|
||||||
|
"Test Serial and Batch Bundle Company Item",
|
||||||
properties={
|
properties={
|
||||||
"has_serial_no": 1,
|
"has_serial_no": 1,
|
||||||
"serial_no_series": "TT-SER-VAL-.#####",
|
"serial_no_series": "TT-SER-VAL-.#####",
|
||||||
}
|
},
|
||||||
)
|
).name
|
||||||
|
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
item_code=item,
|
item_code=item,
|
||||||
@ -460,6 +461,26 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
|||||||
sn_doc = add_serial_batch_ledgers(entries, item_row, pr, "_Test Warehouse - _TC")
|
sn_doc = add_serial_batch_ledgers(entries, item_row, pr, "_Test Warehouse - _TC")
|
||||||
self.assertEqual(sn_doc.company, "_Test Company")
|
self.assertEqual(sn_doc.company, "_Test Company")
|
||||||
|
|
||||||
|
def test_auto_cancel_serial_and_batch(self):
|
||||||
|
item_code = make_item(
|
||||||
|
properties={"has_serial_no": 1, "serial_no_series": "ATC-TT-SER-VAL-.#####"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
bundle = se.items[0].serial_and_batch_bundle
|
||||||
|
docstatus = frappe.db.get_value("Serial and Batch Bundle", bundle, "docstatus")
|
||||||
|
self.assertEqual(docstatus, 1)
|
||||||
|
|
||||||
|
se.cancel()
|
||||||
|
docstatus = frappe.db.get_value("Serial and Batch Bundle", bundle, "docstatus")
|
||||||
|
self.assertEqual(docstatus, 2)
|
||||||
|
|
||||||
|
|
||||||
def get_batch_from_bundle(bundle):
|
def get_batch_from_bundle(bundle):
|
||||||
from erpnext.stock.serial_batch_bundle import get_batch_nos
|
from erpnext.stock.serial_batch_bundle import get_batch_nos
|
||||||
|
@ -25,7 +25,12 @@ from frappe.utils import (
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
||||||
from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, validate_bom_no
|
from erpnext.manufacturing.doctype.bom.bom import (
|
||||||
|
add_additional_cost,
|
||||||
|
get_op_cost_from_sub_assemblies,
|
||||||
|
get_scrap_items_from_sub_assemblies,
|
||||||
|
validate_bom_no,
|
||||||
|
)
|
||||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
@ -1898,11 +1903,22 @@ class StockEntry(StockController):
|
|||||||
def get_bom_scrap_material(self, qty):
|
def get_bom_scrap_material(self, qty):
|
||||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||||
|
|
||||||
# item dict = { item_code: {qty, description, stock_uom} }
|
if (
|
||||||
item_dict = (
|
frappe.db.get_single_value(
|
||||||
get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1)
|
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||||
or {}
|
)
|
||||||
)
|
and self.work_order
|
||||||
|
and frappe.get_cached_value("Work Order", self.work_order, "use_multi_level_bom")
|
||||||
|
):
|
||||||
|
item_dict = get_scrap_items_from_sub_assemblies(self.bom_no, self.company, qty)
|
||||||
|
else:
|
||||||
|
# item dict = { item_code: {qty, description, stock_uom} }
|
||||||
|
item_dict = (
|
||||||
|
get_bom_items_as_dict(
|
||||||
|
self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||||
|
)
|
||||||
|
or {}
|
||||||
|
)
|
||||||
|
|
||||||
for item in item_dict.values():
|
for item in item_dict.values():
|
||||||
item.from_warehouse = ""
|
item.from_warehouse = ""
|
||||||
@ -2653,6 +2669,15 @@ def get_work_order_details(work_order, company):
|
|||||||
def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||||
operating_cost_per_unit = 0
|
operating_cost_per_unit = 0
|
||||||
if work_order:
|
if work_order:
|
||||||
|
if (
|
||||||
|
bom_no
|
||||||
|
and frappe.db.get_single_value(
|
||||||
|
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||||
|
)
|
||||||
|
and frappe.get_cached_value("Work Order", work_order, "use_multi_level_bom")
|
||||||
|
):
|
||||||
|
return get_op_cost_from_sub_assemblies(bom_no)
|
||||||
|
|
||||||
if not bom_no:
|
if not bom_no:
|
||||||
bom_no = work_order.bom_no
|
bom_no = work_order.bom_no
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ def create_material_request(material_requests):
|
|||||||
exceptions_list.extend(frappe.local.message_log)
|
exceptions_list.extend(frappe.local.message_log)
|
||||||
frappe.local.message_log = []
|
frappe.local.message_log = []
|
||||||
else:
|
else:
|
||||||
exceptions_list.append(frappe.get_traceback())
|
exceptions_list.append(frappe.get_traceback(with_context=True))
|
||||||
|
|
||||||
mr.log_error("Unable to create material request")
|
mr.log_error("Unable to create material request")
|
||||||
|
|
||||||
|
@ -242,6 +242,12 @@ class SerialBatchBundle:
|
|||||||
if self.item_details.has_batch_no == 1:
|
if self.item_details.has_batch_no == 1:
|
||||||
self.update_batch_qty()
|
self.update_batch_qty()
|
||||||
|
|
||||||
|
if self.sle.is_cancelled and self.sle.serial_and_batch_bundle:
|
||||||
|
self.cancel_serial_and_batch_bundle()
|
||||||
|
|
||||||
|
def cancel_serial_and_batch_bundle(self):
|
||||||
|
frappe.get_cached_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle).cancel()
|
||||||
|
|
||||||
def submit_serial_and_batch_bundle(self):
|
def submit_serial_and_batch_bundle(self):
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
|
||||||
self.validate_actual_qty(doc)
|
self.validate_actual_qty(doc)
|
||||||
|
@ -195,7 +195,6 @@ class TestFIFOValuation(unittest.TestCase):
|
|||||||
total_value -= sum(q * r for q, r in consumed)
|
total_value -= sum(q * r for q, r in consumed)
|
||||||
self.assertTotalQty(total_qty)
|
self.assertTotalQty(total_qty)
|
||||||
self.assertTotalValue(total_value)
|
self.assertTotalValue(total_value)
|
||||||
self.assertGreaterEqual(total_value, 0)
|
|
||||||
|
|
||||||
|
|
||||||
class TestLIFOValuation(unittest.TestCase):
|
class TestLIFOValuation(unittest.TestCase):
|
||||||
|
@ -62,7 +62,7 @@ def retry_failed_transactions(failed_docs: list | None):
|
|||||||
task(log.transaction_name, log.from_doctype, log.to_doctype)
|
task(log.transaction_name, log.from_doctype, log.to_doctype)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback(save_point="before_creation_state")
|
frappe.db.rollback(save_point="before_creation_state")
|
||||||
update_log(log.name, "Failed", 1, str(frappe.get_traceback()))
|
update_log(log.name, "Failed", 1, str(frappe.get_traceback(with_context=True)))
|
||||||
else:
|
else:
|
||||||
update_log(log.name, "Success", 1)
|
update_log(log.name, "Success", 1)
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ def job(deserialized_data, from_doctype, to_doctype):
|
|||||||
fail_count += 1
|
fail_count += 1
|
||||||
create_log(
|
create_log(
|
||||||
doc_name,
|
doc_name,
|
||||||
str(frappe.get_traceback()),
|
str(frappe.get_traceback(with_context=True)),
|
||||||
from_doctype,
|
from_doctype,
|
||||||
to_doctype,
|
to_doctype,
|
||||||
status="Failed",
|
status="Failed",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user