From e58a41a8a263b5cd5b65b40bcb6a16da0276987d Mon Sep 17 00:00:00 2001 From: Kanchan Chauhan Date: Tue, 17 Oct 2017 15:17:24 +0530 Subject: [PATCH] Added maintenance to Asset and enhanced maintenance for Asset Maintenance --- erpnext/accounts/doctype/asset/asset.js | 293 +--- erpnext/accounts/doctype/asset/asset.json | 442 +++++- erpnext/accounts/doctype/asset/asset.py | 254 +--- erpnext/accounts/doctype/asset/test_asset.js | 23 + erpnext/accounts/doctype/asset/test_asset.py | 8 +- .../doctype/asset_category/asset_category.js | 42 +- .../asset_category/asset_category.json | 20 +- .../doctype/asset_category/asset_category.py | 9 +- .../asset_category/test_asset_category.js | 23 + .../asset_category/test_asset_category.py | 23 +- .../doctype/asset_movement/asset_movement.js | 14 +- .../asset_movement/asset_movement.json | 20 +- .../doctype/asset_movement/asset_movement.py | 42 +- .../asset_movement/test_asset_movement.js | 23 + .../asset_movement/test_asset_movement.py | 45 +- .../depreciation_schedule.json | 25 +- .../depreciation_schedule.py | 2 +- .../doctype/sales_invoice/sales_invoice.js | 2 +- .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/assets/__init__.py | 0 erpnext/assets/doctype/__init__.py | 0 erpnext/assets/doctype/asset/__init__.py | 0 erpnext/assets/doctype/asset/asset.js | 304 ++++ erpnext/assets/doctype/asset/asset.json | 1228 +++++++++++++++++ erpnext/assets/doctype/asset/asset.py | 271 ++++ .../assets/doctype/asset/asset_dashboard.py | 14 + .../doctype/asset/asset_list.js | 0 .../doctype/asset/depreciation.py | 0 erpnext/assets/doctype/asset/test_asset.js | 23 + erpnext/assets/doctype/asset/test_asset.py | 343 +++++ .../assets/doctype/asset_category/__init__.py | 0 .../doctype/asset_category/asset_category.js | 44 + .../asset_category/asset_category.json | 295 ++++ .../doctype/asset_category/asset_category.py | 15 + .../asset_category/test_asset_category.js | 23 + .../asset_category/test_asset_category.py | 29 + .../doctype/asset_maintenance/__init__.py | 0 .../asset_maintenance/asset_maintenance.js | 105 ++ .../asset_maintenance/asset_maintenance.json | 459 ++++++ .../asset_maintenance/asset_maintenance.py | 71 + .../test_asset_maintenance.js | 23 + .../test_asset_maintenance.py | 139 ++ .../doctype/asset_maintenance_log/__init__.py | 0 .../asset_maintenance_log.js | 15 + .../asset_maintenance_log.json | 763 ++++++++++ .../asset_maintenance_log.py | 68 + .../asset_maintenance_log_calendar.js | 22 + .../asset_maintenance_log_list.js | 14 + .../test_asset_maintenance_log.js | 23 + .../test_asset_maintenance_log.py | 9 + .../asset_maintenance_task/__init__.py | 0 .../asset_maintenance_task.json | 643 +++++++++ .../asset_maintenance_task.py | 10 + .../asset_maintenance_team/__init__.py | 0 .../asset_maintenance_team.js | 8 + .../asset_maintenance_team.json | 275 ++++ .../asset_maintenance_team.py | 10 + .../test_asset_maintenance_team.js | 23 + .../test_asset_maintenance_team.py | 9 + .../assets/doctype/asset_movement/__init__.py | 0 .../doctype/asset_movement/asset_movement.js | 18 + .../asset_movement/asset_movement.json | 315 +++++ .../doctype/asset_movement/asset_movement.py | 48 + .../asset_movement/test_asset_movement.js | 23 + .../asset_movement/test_asset_movement.py | 51 + .../assets/doctype/asset_repair/__init__.py | 0 .../doctype/asset_repair/asset_repair.js | 21 + .../doctype/asset_repair/asset_repair.json | 718 ++++++++++ .../doctype/asset_repair/asset_repair.py | 20 + .../doctype/asset_repair/test_asset_repair.js | 23 + .../doctype/asset_repair/test_asset_repair.py | 9 + .../doctype/depreciation_schedule/__init__.py | 0 .../depreciation_schedule.json | 226 +++ .../depreciation_schedule.py | 10 + .../maintenance_team_member/__init__.py | 0 .../maintenance_team_member.js | 8 + .../maintenance_team_member.json | 136 ++ .../maintenance_team_member.py | 10 + .../test_maintenance_team_member.js | 23 + .../test_maintenance_team_member.py | 9 + erpnext/assets/report/__init__.py | 0 .../report/asset_maintenance/__init__.py | 0 .../asset_maintenance/asset_maintenance.json | 24 + erpnext/config/accounts.py | 13 - erpnext/config/assets.py | 77 ++ erpnext/config/desktop.py | 8 +- erpnext/demo/user/fixed_asset.py | 4 +- .../{accounts => asset}/asset-category.png | Bin .../img/{accounts => asset}/asset-graph.png | Bin .../img/{accounts => asset}/asset-item.png | Bin .../asset-movement-using-button.png | Bin .../{accounts => asset}/asset-movement.png | Bin .../asset-purchase-invoice.png | Bin .../img/{accounts => asset}/asset-sales.png | Bin .../assets/img/{accounts => asset}/asset.png | Bin .../assets/img/asset/asset_maintenance.png | Bin 0 -> 275751 bytes .../img/asset/asset_maintenance_log.png | Bin 0 -> 275112 bytes .../img/asset/asset_maintenance_team.png | Bin 0 -> 209777 bytes .../docs/assets/img/asset/asset_repair.png | Bin 0 -> 206931 bytes .../depreciation-entry.png | Bin .../depreciation-schedule.png | Bin .../assets/img/asset/maintenance_required.png | Bin 0 -> 69898 bytes .../scrap-journal-entry.png | Bin .../user/manual/en/asset/asset-maintenance.md | 31 + .../assets.md} | 22 +- erpnext/hooks.py | 2 +- .../maintenance_schedule.json | 8 +- .../test_maintenance_schedule.js | 23 + .../maintenance_schedule_item.json | 25 +- erpnext/modules.txt | 3 +- erpnext/patches.txt | 1 + .../v9_0/update_asset_module_doctypes.py | 12 + 112 files changed, 7678 insertions(+), 833 deletions(-) create mode 100644 erpnext/accounts/doctype/asset/test_asset.js create mode 100644 erpnext/accounts/doctype/asset_category/test_asset_category.js create mode 100644 erpnext/accounts/doctype/asset_movement/test_asset_movement.js create mode 100644 erpnext/assets/__init__.py create mode 100644 erpnext/assets/doctype/__init__.py create mode 100644 erpnext/assets/doctype/asset/__init__.py create mode 100644 erpnext/assets/doctype/asset/asset.js create mode 100644 erpnext/assets/doctype/asset/asset.json create mode 100644 erpnext/assets/doctype/asset/asset.py create mode 100644 erpnext/assets/doctype/asset/asset_dashboard.py rename erpnext/{accounts => assets}/doctype/asset/asset_list.js (100%) rename erpnext/{accounts => assets}/doctype/asset/depreciation.py (100%) create mode 100644 erpnext/assets/doctype/asset/test_asset.js create mode 100644 erpnext/assets/doctype/asset/test_asset.py create mode 100644 erpnext/assets/doctype/asset_category/__init__.py create mode 100644 erpnext/assets/doctype/asset_category/asset_category.js create mode 100644 erpnext/assets/doctype/asset_category/asset_category.json create mode 100644 erpnext/assets/doctype/asset_category/asset_category.py create mode 100644 erpnext/assets/doctype/asset_category/test_asset_category.js create mode 100644 erpnext/assets/doctype/asset_category/test_asset_category.py create mode 100644 erpnext/assets/doctype/asset_maintenance/__init__.py create mode 100644 erpnext/assets/doctype/asset_maintenance/asset_maintenance.js create mode 100644 erpnext/assets/doctype/asset_maintenance/asset_maintenance.json create mode 100644 erpnext/assets/doctype/asset_maintenance/asset_maintenance.py create mode 100644 erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js create mode 100644 erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py create mode 100644 erpnext/assets/doctype/asset_maintenance_log/__init__.py create mode 100644 erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js create mode 100644 erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json create mode 100644 erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py create mode 100644 erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_calendar.js create mode 100644 erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js create mode 100644 erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js create mode 100644 erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py create mode 100644 erpnext/assets/doctype/asset_maintenance_task/__init__.py create mode 100644 erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json create mode 100644 erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py create mode 100644 erpnext/assets/doctype/asset_maintenance_team/__init__.py create mode 100644 erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.js create mode 100644 erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json create mode 100644 erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.py create mode 100644 erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js create mode 100644 erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py create mode 100644 erpnext/assets/doctype/asset_movement/__init__.py create mode 100644 erpnext/assets/doctype/asset_movement/asset_movement.js create mode 100644 erpnext/assets/doctype/asset_movement/asset_movement.json create mode 100644 erpnext/assets/doctype/asset_movement/asset_movement.py create mode 100644 erpnext/assets/doctype/asset_movement/test_asset_movement.js create mode 100644 erpnext/assets/doctype/asset_movement/test_asset_movement.py create mode 100644 erpnext/assets/doctype/asset_repair/__init__.py create mode 100644 erpnext/assets/doctype/asset_repair/asset_repair.js create mode 100644 erpnext/assets/doctype/asset_repair/asset_repair.json create mode 100644 erpnext/assets/doctype/asset_repair/asset_repair.py create mode 100644 erpnext/assets/doctype/asset_repair/test_asset_repair.js create mode 100644 erpnext/assets/doctype/asset_repair/test_asset_repair.py create mode 100644 erpnext/assets/doctype/depreciation_schedule/__init__.py create mode 100644 erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json create mode 100644 erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.py create mode 100644 erpnext/assets/doctype/maintenance_team_member/__init__.py create mode 100644 erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.js create mode 100644 erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.json create mode 100644 erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.py create mode 100644 erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js create mode 100644 erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py create mode 100644 erpnext/assets/report/__init__.py create mode 100644 erpnext/assets/report/asset_maintenance/__init__.py create mode 100644 erpnext/assets/report/asset_maintenance/asset_maintenance.json create mode 100644 erpnext/config/assets.py rename erpnext/docs/assets/img/{accounts => asset}/asset-category.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset-graph.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset-item.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset-movement-using-button.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset-movement.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset-purchase-invoice.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset-sales.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/asset.png (100%) create mode 100644 erpnext/docs/assets/img/asset/asset_maintenance.png create mode 100644 erpnext/docs/assets/img/asset/asset_maintenance_log.png create mode 100644 erpnext/docs/assets/img/asset/asset_maintenance_team.png create mode 100644 erpnext/docs/assets/img/asset/asset_repair.png rename erpnext/docs/assets/img/{accounts => asset}/depreciation-entry.png (100%) rename erpnext/docs/assets/img/{accounts => asset}/depreciation-schedule.png (100%) create mode 100644 erpnext/docs/assets/img/asset/maintenance_required.png rename erpnext/docs/assets/img/{accounts => asset}/scrap-journal-entry.png (100%) create mode 100644 erpnext/docs/user/manual/en/asset/asset-maintenance.md rename erpnext/docs/user/manual/en/{accounts/managing-fixed-assets.md => asset/assets.md} (86%) create mode 100644 erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js create mode 100644 erpnext/patches/v9_0/update_asset_module_doctypes.py diff --git a/erpnext/accounts/doctype/asset/asset.js b/erpnext/accounts/doctype/asset/asset.js index e3afc359b6..6693d3be0d 100644 --- a/erpnext/accounts/doctype/asset/asset.js +++ b/erpnext/accounts/doctype/asset/asset.js @@ -1,299 +1,8 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.provide("erpnext.asset"); - frappe.ui.form.on('Asset', { - onload: function(frm) { - frm.set_query("item_code", function() { - return { - "filters": { - "disabled": 0, - "is_fixed_asset": 1, - "is_stock_item": 0 - } - }; - }); - - frm.set_query("warehouse", function() { - return { - "filters": { - "company": frm.doc.company, - "is_group": 0 - } - }; - }); - }, - refresh: function(frm) { - frappe.ui.form.trigger("Asset", "is_existing_asset"); - frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); - frm.events.make_schedules_editable(frm); - if (frm.doc.docstatus==1) { - if (frm.doc.status=='Submitted' && !frm.doc.is_existing_asset && !frm.doc.purchase_invoice) { - frm.add_custom_button("Make Purchase Invoice", function() { - erpnext.asset.make_purchase_invoice(frm); - }); - } - if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) { - frm.add_custom_button("Transfer Asset", function() { - erpnext.asset.transfer_asset(frm); - }); - - frm.add_custom_button("Scrap Asset", function() { - erpnext.asset.scrap_asset(frm); - }); - - frm.add_custom_button("Sell Asset", function() { - erpnext.asset.make_sales_invoice(frm); - }); - - } else if (frm.doc.status=='Scrapped') { - frm.add_custom_button("Restore Asset", function() { - erpnext.asset.restore_asset(frm); - }); - } - - frm.trigger("setup_chart"); - } - }, - - setup_chart: function(frm) { - var x_intervals = [frm.doc.purchase_date]; - var asset_values = [frm.doc.gross_purchase_amount]; - var last_depreciation_date = frm.doc.purchase_date; - - if(frm.doc.opening_accumulated_depreciation) { - last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date, - -1*frm.doc.frequency_of_depreciation); - - x_intervals.push(last_depreciation_date); - asset_values.push(flt(frm.doc.gross_purchase_amount) - - flt(frm.doc.opening_accumulated_depreciation)); - } - - $.each(frm.doc.schedules || [], function(i, v) { - x_intervals.push(v.schedule_date); - var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount); - if(v.journal_entry) { - last_depreciation_date = v.schedule_date; - asset_values.push(asset_value) - } else { - if (in_list(["Scrapped", "Sold"], frm.doc.status)) { - asset_values.push(null) - } else { - asset_values.push(asset_value) - } - } - }) - - if(in_list(["Scrapped", "Sold"], frm.doc.status)) { - x_intervals.push(frm.doc.disposal_date); - asset_values.push(0); - last_depreciation_date = frm.doc.disposal_date; - } - - frm.dashboard.render_graph({ - title: "Asset Value", - data: { - labels: x_intervals, - datasets: [{ - color: 'green', - values: asset_values, - formatted: asset_values.map(d => d.toFixed(2)) - }] - }, - type: 'line' - }); - }, - - - item_code: function(frm) { - if(frm.doc.item_code) { - frappe.call({ - method: "erpnext.accounts.doctype.asset.asset.get_item_details", - args: { - item_code: frm.doc.item_code - }, - callback: function(r, rt) { - if(r.message) { - $.each(r.message, function(field, value) { - frm.set_value(field, value); - }) - } - } - }) - } - }, - - is_existing_asset: function(frm) { - frm.toggle_enable("supplier", frm.doc.is_existing_asset); - frm.toggle_reqd("next_depreciation_date", !frm.doc.is_existing_asset); - }, - - opening_accumulated_depreciation: function(frm) { - erpnext.asset.set_accululated_depreciation(frm); - }, - - depreciation_method: function(frm) { - frm.events.make_schedules_editable(frm); - }, - - make_schedules_editable: function(frm) { - var is_editable = frm.doc.depreciation_method==="Manual" ? true : false; - frm.toggle_enable("schedules", is_editable); - frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); - frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable); } - }); - -frappe.ui.form.on('Depreciation Schedule', { - make_depreciation_entry: function(frm, cdt, cdn) { - var row = locals[cdt][cdn]; - if (!row.journal_entry) { - frappe.call({ - method: "erpnext.accounts.doctype.asset.depreciation.make_depreciation_entry", - args: { - "asset_name": frm.doc.name, - "date": row.schedule_date - }, - callback: function(r) { - frappe.model.sync(r.message); - frm.refresh(); - } - }) - } - }, - - depreciation_amount: function(frm, cdt, cdn) { - erpnext.asset.set_accululated_depreciation(frm); - } - -}) - -erpnext.asset.set_accululated_depreciation = function(frm) { - if(frm.doc.depreciation_method != "Manual") return; - - var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation); - $.each(frm.doc.schedules || [], function(i, row) { - accumulated_depreciation += flt(row.depreciation_amount); - frappe.model.set_value(row.doctype, row.name, - "accumulated_depreciation_amount", accumulated_depreciation); - }) -} - -erpnext.asset.make_purchase_invoice = function(frm) { - frappe.call({ - args: { - "asset": frm.doc.name, - "item_code": frm.doc.item_code, - "gross_purchase_amount": frm.doc.gross_purchase_amount, - "company": frm.doc.company, - "posting_date": frm.doc.purchase_date - }, - method: "erpnext.accounts.doctype.asset.asset.make_purchase_invoice", - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - } - }) -} - -erpnext.asset.make_sales_invoice = function(frm) { - frappe.call({ - args: { - "asset": frm.doc.name, - "item_code": frm.doc.item_code, - "company": frm.doc.company - }, - method: "erpnext.accounts.doctype.asset.asset.make_sales_invoice", - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - } - }) -} - -erpnext.asset.scrap_asset = function(frm) { - frappe.confirm(__("Do you really want to scrap this asset?"), function () { - frappe.call({ - args: { - "asset_name": frm.doc.name - }, - method: "erpnext.accounts.doctype.asset.depreciation.scrap_asset", - callback: function(r) { - cur_frm.reload_doc(); - } - }) - }) -} - -erpnext.asset.restore_asset = function(frm) { - frappe.confirm(__("Do you really want to restore this scrapped asset?"), function () { - frappe.call({ - args: { - "asset_name": frm.doc.name - }, - method: "erpnext.accounts.doctype.asset.depreciation.restore_asset", - callback: function(r) { - cur_frm.reload_doc(); - } - }) - }) -} - -erpnext.asset.transfer_asset = function(frm) { - var dialog = new frappe.ui.Dialog({ - title: __("Transfer Asset"), - fields: [ - { - "label": __("Target Warehouse"), - "fieldname": "target_warehouse", - "fieldtype": "Link", - "options": "Warehouse", - "get_query": function () { - return { - filters: [ - ["Warehouse", "company", "in", ["", cstr(frm.doc.company)]], - ["Warehouse", "is_group", "=", 0] - ] - } - }, - "reqd": 1 - }, - { - "label": __("Date"), - "fieldname": "transfer_date", - "fieldtype": "Datetime", - "reqd": 1, - "default": frappe.datetime.now_datetime() - } - ] - }); - - dialog.set_primary_action(__("Transfer"), function() { - var args = dialog.get_values(); - if(!args) return; - dialog.hide(); - return frappe.call({ - type: "GET", - method: "erpnext.accounts.doctype.asset.asset.transfer_asset", - args: { - args: { - "asset": frm.doc.name, - "transaction_date": args.transfer_date, - "source_warehouse": frm.doc.warehouse, - "target_warehouse": args.target_warehouse, - "company": frm.doc.company - } - }, - freeze: true, - callback: function(r) { - cur_frm.reload_doc(); - } - }) - }); - dialog.show(); -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/asset/asset.json b/erpnext/accounts/doctype/asset/asset.json index 469daa05ae..727645516d 100644 --- a/erpnext/accounts/doctype/asset/asset.json +++ b/erpnext/accounts/doctype/asset/asset.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:asset_name", @@ -12,6 +13,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -41,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -71,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -101,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -131,6 +136,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -148,7 +154,7 @@ "label": "Status", "length": 0, "no_copy": 1, - "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped", + "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Repair\nOut of Order", "permlevel": 0, "precision": "", "print_hide": 0, @@ -162,6 +168,102 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_owner", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Asset Owner", + "length": 0, + "no_copy": 0, + "options": "Company\nSupplier\nCustomer", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.asset_owner == \"Supplier\"", + "fieldname": "supplier", + "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": "Supplier", + "length": 0, + "no_copy": 0, + "options": "Supplier", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.asset_owner == \"Customer\"", + "fieldname": "customer", + "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": "Customer", + "length": 0, + "no_copy": 0, + "options": "Customer", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 1, "bold": 0, "collapsible": 0, @@ -191,6 +293,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -219,6 +322,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -249,6 +353,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -279,12 +384,13 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "is_existing_asset", - "fieldtype": "Check", + "fieldname": "department", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -292,9 +398,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Is Existing Asset", + "label": "Department", "length": 0, "no_copy": 0, + "options": "Department", "permlevel": 0, "precision": "", "print_hide": 0, @@ -308,6 +415,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -337,36 +445,7 @@ "unique": 0 }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier", - "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": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "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, - "unique": 0 - }, - { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -397,6 +476,37 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_existing_asset", + "fieldtype": "Check", + "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": "Is Existing Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -426,6 +536,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -456,6 +567,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -484,6 +596,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -514,6 +627,36 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -545,6 +688,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -576,42 +720,13 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "value_after_depreciation", - "fieldtype": "Currency", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Value After Depreciation", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", + "fieldname": "section_break_20", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -634,6 +749,68 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "calculate_depreciation", + "fieldtype": "Check", + "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": "Calculate Depreciation", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "calculate_depreciation", + "fieldname": "section_break_23", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Depreciation", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -666,6 +843,38 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "value_after_depreciation", + "fieldtype": "Currency", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Value After Depreciation", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -695,6 +904,36 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_24", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -725,6 +964,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -754,6 +994,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -784,10 +1025,12 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "calculate_depreciation", "fieldname": "section_break_14", "fieldtype": "Section Break", "hidden": 0, @@ -813,6 +1056,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -843,6 +1087,68 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_31", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maintenance", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Check if Asset requires Preventive Maintenance or Calibration", + "fieldname": "maintenance_required", + "fieldtype": "Check", + "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": "Maintenance Required", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -872,18 +1178,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 72, "image_field": "image", "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-17 16:26:19.111939", + "modified": "2017-10-28 19:59:17.040684", "modified_by": "Administrator", "module": "Accounts", "name": "Asset", diff --git a/erpnext/accounts/doctype/asset/asset.py b/erpnext/accounts/doctype/asset/asset.py index dd1e491680..942a59e135 100644 --- a/erpnext/accounts/doctype/asset/asset.py +++ b/erpnext/accounts/doctype/asset/asset.py @@ -1,260 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe -from frappe import _ -from frappe.utils import flt, add_months, cint, nowdate, getdate from frappe.model.document import Document -from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account -from erpnext.accounts.doctype.asset.depreciation \ - import get_disposal_account_and_cost_center, get_depreciation_accounts class Asset(Document): - def validate(self): - self.status = self.get_status() - self.validate_item() - self.set_missing_values() - self.validate_asset_values() - self.make_depreciation_schedule() - self.set_accumulated_depreciation() - if self.get("schedules"): - self.validate_expected_value_after_useful_life() - # Validate depreciation related accounts - get_depreciation_accounts(self) - - def on_submit(self): - self.set_status() - - def on_cancel(self): - self.validate_cancellation() - self.delete_depreciation_entries() - self.set_status() - - def validate_item(self): - item = frappe.db.get_value("Item", self.item_code, - ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1) - if not item: - frappe.throw(_("Item {0} does not exist").format(self.item_code)) - elif item.disabled: - frappe.throw(_("Item {0} has been disabled").format(self.item_code)) - elif not item.is_fixed_asset: - frappe.throw(_("Item {0} must be a Fixed Asset Item").format(self.item_code)) - elif item.is_stock_item: - frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code)) - - def set_missing_values(self): - if self.item_code: - item_details = get_item_details(self.item_code) - for field, value in item_details.items(): - if not self.get(field): - self.set(field, value) - - self.value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) - - def validate_asset_values(self): - if flt(self.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): - frappe.throw(_("Expected Value After Useful Life must be less than Gross Purchase Amount")) - - if not flt(self.gross_purchase_amount): - frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) - - if not self.is_existing_asset: - self.opening_accumulated_depreciation = 0 - self.number_of_depreciations_booked = 0 - if not self.next_depreciation_date: - frappe.throw(_("Next Depreciation Date is mandatory for new asset")) - else: - depreciable_amount = flt(self.gross_purchase_amount) - flt(self.expected_value_after_useful_life) - if flt(self.opening_accumulated_depreciation) > depreciable_amount: - frappe.throw(_("Opening Accumulated Depreciation must be less than equal to {0}") - .format(depreciable_amount)) - - if self.opening_accumulated_depreciation: - if not self.number_of_depreciations_booked: - frappe.throw(_("Please set Number of Depreciations Booked")) - else: - self.number_of_depreciations_booked = 0 - - if cint(self.number_of_depreciations_booked) > cint(self.total_number_of_depreciations): - frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) - - if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(nowdate()): - frappe.msgprint(_("Next Depreciation Date is entered as past date"), title=_('Warning'), indicator='red') - - if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.purchase_date): - frappe.throw(_("Next Depreciation Date cannot be before Purchase Date")) - - if (flt(self.value_after_depreciation) > flt(self.expected_value_after_useful_life) - and not self.next_depreciation_date): - frappe.throw(_("Please set Next Depreciation Date")) - - def make_depreciation_schedule(self): - if self.depreciation_method != 'Manual': - self.schedules = [] - - if not self.get("schedules") and self.next_depreciation_date: - value_after_depreciation = flt(self.value_after_depreciation) - - number_of_pending_depreciations = cint(self.total_number_of_depreciations) - \ - cint(self.number_of_depreciations_booked) - if number_of_pending_depreciations: - for n in xrange(number_of_pending_depreciations): - schedule_date = add_months(self.next_depreciation_date, - n * cint(self.frequency_of_depreciation)) - - depreciation_amount = self.get_depreciation_amount(value_after_depreciation) - value_after_depreciation -= flt(depreciation_amount) - - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount - }) - - def set_accumulated_depreciation(self): - accumulated_depreciation = flt(self.opening_accumulated_depreciation) - value_after_depreciation = flt(self.value_after_depreciation) - for i, d in enumerate(self.get("schedules")): - depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount")) - value_after_depreciation -= flt(depreciation_amount) - - if i==len(self.get("schedules"))-1 and self.depreciation_method == "Straight Line": - depreciation_amount += flt(value_after_depreciation - flt(self.expected_value_after_useful_life), - d.precision("depreciation_amount")) - - d.depreciation_amount = depreciation_amount - accumulated_depreciation += d.depreciation_amount - d.accumulated_depreciation_amount = flt(accumulated_depreciation, d.precision("accumulated_depreciation_amount")) - - def get_depreciation_amount(self, depreciable_value): - if self.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = (flt(self.value_after_depreciation) - - flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - - cint(self.number_of_depreciations_booked)) - else: - factor = 200.0 / self.total_number_of_depreciations - depreciation_amount = flt(depreciable_value * factor / 100, 0) - - value_after_depreciation = flt(depreciable_value) - depreciation_amount - if value_after_depreciation < flt(self.expected_value_after_useful_life): - depreciation_amount = flt(depreciable_value) - flt(self.expected_value_after_useful_life) - - return depreciation_amount - - def validate_expected_value_after_useful_life(self): - accumulated_depreciation_after_full_schedule = \ - max([d.accumulated_depreciation_amount for d in self.get("schedules")]) - - asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule), - self.precision('expected_value_after_useful_life')) - - if self.expected_value_after_useful_life < asset_value_after_full_schedule: - frappe.throw(_("Expected value after useful life must be greater than or equal to {0}") - .format(asset_value_after_full_schedule)) - - def validate_cancellation(self): - if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): - frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) - - if self.purchase_invoice: - frappe.throw(_("Please cancel Purchase Invoice {0} first").format(self.purchase_invoice)) - - def delete_depreciation_entries(self): - for d in self.get("schedules"): - if d.journal_entry: - frappe.get_doc("Journal Entry", d.journal_entry).cancel() - d.db_set("journal_entry", None) - - self.db_set("value_after_depreciation", - (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation))) - - def set_status(self, status=None): - '''Get and update status''' - if not status: - status = self.get_status() - self.db_set("status", status) - - def get_status(self): - '''Returns status based on whether it is draft, submitted, scrapped or depreciated''' - if self.docstatus == 0: - status = "Draft" - elif self.docstatus == 1: - status = "Submitted" - if self.journal_entry_for_scrap: - status = "Scrapped" - elif flt(self.value_after_depreciation) <= flt(self.expected_value_after_useful_life): - status = "Fully Depreciated" - elif flt(self.value_after_depreciation) < flt(self.gross_purchase_amount): - status = 'Partially Depreciated' - elif self.docstatus == 2: - status = "Cancelled" - - return status - -@frappe.whitelist() -def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, posting_date): - pi = frappe.new_doc("Purchase Invoice") - pi.company = company - pi.currency = frappe.db.get_value("Company", company, "default_currency") - pi.set_posting_time = 1 - pi.posting_date = posting_date - pi.append("items", { - "item_code": item_code, - "is_fixed_asset": 1, - "asset": asset, - "expense_account": get_fixed_asset_account(asset), - "qty": 1, - "price_list_rate": gross_purchase_amount, - "rate": gross_purchase_amount - }) - pi.set_missing_values() - return pi - -@frappe.whitelist() -def make_sales_invoice(asset, item_code, company): - si = frappe.new_doc("Sales Invoice") - si.company = company - si.currency = frappe.db.get_value("Company", company, "default_currency") - disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(company) - si.append("items", { - "item_code": item_code, - "is_fixed_asset": 1, - "asset": asset, - "income_account": disposal_account, - "cost_center": depreciation_cost_center, - "qty": 1 - }) - si.set_missing_values() - return si - -@frappe.whitelist() -def transfer_asset(args): - import json - args = json.loads(args) - movement_entry = frappe.new_doc("Asset Movement") - movement_entry.update(args) - movement_entry.insert() - movement_entry.submit() - - frappe.db.commit() - - frappe.msgprint(_("Asset Movement record {0} created").format("{0}".format(movement_entry.name))) - -@frappe.whitelist() -def get_item_details(item_code): - asset_category = frappe.db.get_value("Item", item_code, "asset_category") - - if not asset_category: - frappe.throw(_("Please enter Asset Category in Item {0}").format(item_code)) - - ret = frappe.db.get_value("Asset Category", asset_category, - ["depreciation_method", "total_number_of_depreciations", "frequency_of_depreciation"], as_dict=1) - - ret.update({ - "asset_category": asset_category - }) - - return ret + pass diff --git a/erpnext/accounts/doctype/asset/test_asset.js b/erpnext/accounts/doctype/asset/test_asset.js new file mode 100644 index 0000000000..6119e38217 --- /dev/null +++ b/erpnext/accounts/doctype/asset/test_asset.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset + () => frappe.tests.make('Asset', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/asset/test_asset.py b/erpnext/accounts/doctype/asset/test_asset.py index fd66d1fb67..22baae36df 100644 --- a/erpnext/accounts/doctype/asset/test_asset.py +++ b/erpnext/accounts/doctype/asset/test_asset.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals import frappe import unittest from frappe.utils import cstr, nowdate, getdate, flt -from erpnext.accounts.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset -from erpnext.accounts.doctype.asset.asset import make_sales_invoice, make_purchase_invoice +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset +from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase_invoice class TestAsset(unittest.TestCase): def setUp(self): @@ -340,4 +340,4 @@ def set_depreciation_settings_in_company(): company.save() # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) diff --git a/erpnext/accounts/doctype/asset_category/asset_category.js b/erpnext/accounts/doctype/asset_category/asset_category.js index aafe8a69a0..f3bb802633 100644 --- a/erpnext/accounts/doctype/asset_category/asset_category.js +++ b/erpnext/accounts/doctype/asset_category/asset_category.js @@ -1,44 +1,8 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt frappe.ui.form.on('Asset Category', { - onload: function(frm) { - frm.add_fetch('company_name', 'accumulated_depreciation_account', 'accumulated_depreciation_account'); - frm.add_fetch('company_name', 'depreciation_expense_account', 'accumulated_depreciation_account'); - - frm.set_query('fixed_asset_account', 'accounts', function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - "filters": { - "account_type": "Fixed Asset", - "root_type": "Asset", - "is_group": 0, - "company": d.company_name - } - }; - }); - - frm.set_query('accumulated_depreciation_account', 'accounts', function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - "filters": { - "root_type": "Asset", - "is_group": 0, - "company": d.company_name - } - }; - }); - - frm.set_query('depreciation_expense_account', 'accounts', function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - "filters": { - "root_type": "Expense", - "is_group": 0, - "company": d.company_name - } - }; - }); + refresh: function(frm) { } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/asset_category/asset_category.json b/erpnext/accounts/doctype/asset_category/asset_category.json index 74762d790c..d34ec5ce3f 100644 --- a/erpnext/accounts/doctype/asset_category/asset_category.json +++ b/erpnext/accounts/doctype/asset_category/asset_category.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:asset_category_name", @@ -13,6 +14,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -24,7 +26,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Asset Category Name", "length": 0, @@ -42,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -54,7 +57,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Depreciation Method", "length": 0, @@ -73,6 +76,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -101,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -112,7 +117,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Total Number of Depreciations", "length": 0, @@ -130,6 +135,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -141,7 +147,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Frequency of Depreciation (Months)", "length": 0, @@ -159,6 +165,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -188,6 +195,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -218,17 +226,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-17 16:09:52.955332", + "modified": "2017-10-28 19:59:31.819567", "modified_by": "Administrator", "module": "Accounts", "name": "Asset Category", diff --git a/erpnext/accounts/doctype/asset_category/asset_category.py b/erpnext/accounts/doctype/asset_category/asset_category.py index 5279c37ea8..dc454abe54 100644 --- a/erpnext/accounts/doctype/asset_category/asset_category.py +++ b/erpnext/accounts/doctype/asset_category/asset_category.py @@ -1,15 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe -from frappe import _ -from frappe.utils import cint from frappe.model.document import Document class AssetCategory(Document): - def validate(self): - for field in ("total_number_of_depreciations", "frequency_of_depreciation"): - if cint(self.get(field))<1: - frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/asset_category/test_asset_category.js b/erpnext/accounts/doctype/asset_category/test_asset_category.js new file mode 100644 index 0000000000..7e343b7519 --- /dev/null +++ b/erpnext/accounts/doctype/asset_category/test_asset_category.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Category", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Category + () => frappe.tests.make('Asset Category', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/asset_category/test_asset_category.py b/erpnext/accounts/doctype/asset_category/test_asset_category.py index b32f9b5020..4d5169a0a6 100644 --- a/erpnext/accounts/doctype/asset_category/test_asset_category.py +++ b/erpnext/accounts/doctype/asset_category/test_asset_category.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals @@ -7,23 +7,4 @@ import frappe import unittest class TestAssetCategory(unittest.TestCase): - def test_mandatory_fields(self): - asset_category = frappe.new_doc("Asset Category") - asset_category.asset_category_name = "Computers" - - self.assertRaises(frappe.MandatoryError, asset_category.insert) - - asset_category.total_number_of_depreciations = 3 - asset_category.frequency_of_depreciation = 3 - asset_category.append("accounts", { - "company_name": "_Test Company", - "fixed_asset_account": "_Test Fixed Asset - _TC", - "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", - "depreciation_expense_account": "_Test Depreciations - _TC" - }) - - try: - asset_category.insert() - except frappe.DuplicateEntryError: - pass - \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/asset_movement/asset_movement.js b/erpnext/accounts/doctype/asset_movement/asset_movement.js index 70c4080d1c..f2d82beaa2 100644 --- a/erpnext/accounts/doctype/asset_movement/asset_movement.js +++ b/erpnext/accounts/doctype/asset_movement/asset_movement.js @@ -1,18 +1,8 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt frappe.ui.form.on('Asset Movement', { - onload: function(frm) { - frm.add_fetch("asset", "warehouse", "source_warehouse"); - - frm.set_query("target_warehouse", function() { - return { - filters: [ - ["Warehouse", "company", "in", ["", cstr(frm.doc.company)]], - ["Warehouse", "is_group", "=", 0] - ] - } - }) + refresh: function(frm) { } }); diff --git a/erpnext/accounts/doctype/asset_movement/asset_movement.json b/erpnext/accounts/doctype/asset_movement/asset_movement.json index a1189f5035..7d7c12730a 100644 --- a/erpnext/accounts/doctype/asset_movement/asset_movement.json +++ b/erpnext/accounts/doctype/asset_movement/asset_movement.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, "autoname": "AM-.#####", @@ -12,6 +13,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -23,7 +25,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Asset", "length": 0, @@ -42,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -53,7 +56,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Transaction Date", "length": 0, @@ -71,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -82,7 +86,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Company", "length": 0, @@ -101,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -129,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -159,6 +165,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -170,7 +177,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Target Warehouse", "length": 0, @@ -189,6 +196,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -218,17 +226,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-17 16:09:32.069344", + "modified": "2017-10-28 20:01:49.372952", "modified_by": "Administrator", "module": "Accounts", "name": "Asset Movement", diff --git a/erpnext/accounts/doctype/asset_movement/asset_movement.py b/erpnext/accounts/doctype/asset_movement/asset_movement.py index 574c49992b..5acd4f1e82 100644 --- a/erpnext/accounts/doctype/asset_movement/asset_movement.py +++ b/erpnext/accounts/doctype/asset_movement/asset_movement.py @@ -1,48 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe -from frappe import _ from frappe.model.document import Document class AssetMovement(Document): - def validate(self): - self.validate_asset() - self.validate_warehouses() - - def validate_asset(self): - status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) - if status in ("Draft", "Scrapped", "Sold"): - frappe.throw(_("{0} asset cannot be transferred").format(status)) - - if company != self.company: - frappe.throw(_("Asset {0} does not belong to company {1}").format(self.asset, self.company)) - - def validate_warehouses(self): - if not self.source_warehouse: - self.source_warehouse = frappe.db.get_value("Asset", self.asset, "warehouse") - - if self.source_warehouse == self.target_warehouse: - frappe.throw(_("Source and Target Warehouse cannot be same")) - - def on_submit(self): - self.set_latest_warehouse_in_asset() - - def on_cancel(self): - self.set_latest_warehouse_in_asset() - - def set_latest_warehouse_in_asset(self): - latest_movement_entry = frappe.db.sql("""select target_warehouse from `tabAsset Movement` - where asset=%s and docstatus=1 and company=%s - order by transaction_date desc limit 1""", (self.asset, self.company)) - - if latest_movement_entry: - warehouse = latest_movement_entry[0][0] - else: - warehouse = frappe.db.sql("""select source_warehouse from `tabAsset Movement` - where asset=%s and docstatus=2 and company=%s - order by transaction_date asc limit 1""", (self.asset, self.company))[0][0] - - frappe.db.set_value("Asset", self.asset, "warehouse", warehouse) \ No newline at end of file + pass diff --git a/erpnext/accounts/doctype/asset_movement/test_asset_movement.js b/erpnext/accounts/doctype/asset_movement/test_asset_movement.js new file mode 100644 index 0000000000..b9515763c4 --- /dev/null +++ b/erpnext/accounts/doctype/asset_movement/test_asset_movement.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Movement", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Movement + () => frappe.tests.make('Asset Movement', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/asset_movement/test_asset_movement.py b/erpnext/accounts/doctype/asset_movement/test_asset_movement.py index 9880efc37f..6f750a4c50 100644 --- a/erpnext/accounts/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/accounts/doctype/asset_movement/test_asset_movement.py @@ -1,51 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals import frappe -from frappe.utils import now import unittest -from erpnext.accounts.doctype.asset.test_asset import create_asset - class TestAssetMovement(unittest.TestCase): - def test_movement(self): - asset = create_asset() - - if asset.docstatus == 0: - asset.submit() - - movement1 = create_asset_movement(asset, target_warehouse="_Test Warehouse 1 - _TC") - self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse 1 - _TC") - - movement2 = create_asset_movement(asset, target_warehouse="_Test Warehouse 2 - _TC") - self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse 2 - _TC") - - movement1.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse 2 - _TC") - - movement2.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse - _TC") - - asset.load_from_db() - asset.cancel() - frappe.delete_doc("Asset", asset.name) - - -def create_asset_movement(asset, target_warehouse, transaction_date=None): - if not transaction_date: - transaction_date = now() - - movement = frappe.new_doc("Asset Movement") - movement.update({ - "asset": asset.name, - "transaction_date": transaction_date, - "target_warehouse": target_warehouse, - "company": asset.company - }) - - movement.insert() - movement.submit() - - return movement + pass diff --git a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json index 1fadf5eb4f..29dcdfb04b 100644 --- a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 1, "autoname": "", @@ -13,6 +14,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -23,7 +25,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Schedule Date", "length": 0, "no_copy": 1, @@ -40,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -50,7 +55,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Depreciation Amount", "length": 0, "no_copy": 1, @@ -68,6 +75,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -78,7 +86,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -94,6 +104,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -104,7 +115,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Accumulated Depreciation Amount", "length": 0, "no_copy": 1, @@ -122,6 +135,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -133,7 +147,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Journal Entry", "length": 0, "no_copy": 1, @@ -151,6 +167,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 1, "bold": 0, "collapsible": 0, @@ -162,7 +179,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Make Depreciation Entry", "length": 0, "no_copy": 0, @@ -179,17 +198,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-11-18 16:42:19.543657", + "modified": "2017-10-28 20:02:11.376361", "modified_by": "Administrator", "module": "Accounts", "name": "Depreciation Schedule", @@ -199,7 +218,9 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.py b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.py index 957d6d119f..54fba3f68c 100644 --- a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.py +++ b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index ccaae49670..82d8de541d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -298,7 +298,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte var row = locals[cdt][cdn]; if(row.asset) { frappe.call({ - method: erpnext.accounts.doctype.asset.depreciation.get_disposal_account_and_cost_center, + method: erpnext.assets.doctype.asset.depreciation.get_disposal_account_and_cost_center, args: { "company": frm.doc.company }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ee2b5542fb..8edeb99ae0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -15,7 +15,7 @@ from erpnext.controllers.selling_controller import SellingController from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data -from erpnext.accounts.doctype.asset.depreciation \ +from erpnext.assets.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no diff --git a/erpnext/assets/__init__.py b/erpnext/assets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/__init__.py b/erpnext/assets/doctype/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset/__init__.py b/erpnext/assets/doctype/asset/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js new file mode 100644 index 0000000000..e7459259c8 --- /dev/null +++ b/erpnext/assets/doctype/asset/asset.js @@ -0,0 +1,304 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.provide("erpnext.asset"); + +frappe.ui.form.on('Asset', { + onload: function(frm) { + frm.set_query("item_code", function() { + return { + "filters": { + "disabled": 0, + "is_fixed_asset": 1, + "is_stock_item": 0 + } + }; + }); + + frm.set_query("warehouse", function() { + return { + "filters": { + "company": frm.doc.company, + "is_group": 0 + } + }; + }); + }, + + refresh: function(frm) { + frappe.ui.form.trigger("Asset", "is_existing_asset"); + frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); + frm.events.make_schedules_editable(frm); + + if (frm.doc.docstatus==1) { + if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) { + frm.add_custom_button("Transfer Asset", function() { + erpnext.asset.transfer_asset(frm); + }); + + frm.add_custom_button("Scrap Asset", function() { + erpnext.asset.scrap_asset(frm); + }); + + frm.add_custom_button("Sell Asset", function() { + erpnext.asset.make_sales_invoice(frm); + }); + + } else if (frm.doc.status=='Scrapped') { + frm.add_custom_button("Restore Asset", function() { + erpnext.asset.restore_asset(frm); + }); + } + if (frm.doc.status=='Submitted' && !frm.doc.is_existing_asset && !frm.doc.purchase_invoice) { + frm.add_custom_button(__("Purchase Invoice"), function() { + frm.trigger("make_purchase_invoice"); + }, __("Make")); + } + if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) { + frm.add_custom_button(__("Asset Maintenance"), function() { + frm.trigger("create_asset_maintenance"); + }, __("Make")); + } + frm.page.set_inner_btn_group_as_primary(__("Make")); + frm.trigger("setup_chart"); + } + }, + + setup_chart: function(frm) { + var x_intervals = [frm.doc.purchase_date]; + var asset_values = [frm.doc.gross_purchase_amount]; + var last_depreciation_date = frm.doc.purchase_date; + + if(frm.doc.opening_accumulated_depreciation) { + last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date, + -1*frm.doc.frequency_of_depreciation); + + x_intervals.push(last_depreciation_date); + asset_values.push(flt(frm.doc.gross_purchase_amount) - + flt(frm.doc.opening_accumulated_depreciation)); + } + + $.each(frm.doc.schedules || [], function(i, v) { + x_intervals.push(v.schedule_date); + var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount); + if(v.journal_entry) { + last_depreciation_date = v.schedule_date; + asset_values.push(asset_value); + } else { + if (in_list(["Scrapped", "Sold"], frm.doc.status)) { + asset_values.push(null); + } else { + asset_values.push(asset_value) + } + } + }); + + if(in_list(["Scrapped", "Sold"], frm.doc.status)) { + x_intervals.push(frm.doc.disposal_date); + asset_values.push(0); + last_depreciation_date = frm.doc.disposal_date; + } + + frm.dashboard.render_graph({ + title: "Asset Value", + data: { + labels: x_intervals, + datasets: [{ + color: 'green', + values: asset_values, + formatted: asset_values.map(d => d.toFixed(2)) + }] + }, + type: 'line' + }); + }, + + + item_code: function(frm) { + if(frm.doc.item_code) { + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.get_item_details", + args: { + item_code: frm.doc.item_code + }, + callback: function(r, rt) { + if(r.message) { + $.each(r.message, function(field, value) { + frm.set_value(field, value); + }) + } + } + }) + } + }, + + is_existing_asset: function(frm) { + frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); + }, + + opening_accumulated_depreciation: function(frm) { + erpnext.asset.set_accululated_depreciation(frm); + }, + + depreciation_method: function(frm) { + frm.events.make_schedules_editable(frm); + }, + + make_schedules_editable: function(frm) { + var is_editable = frm.doc.depreciation_method==="Manual" ? true : false; + frm.toggle_enable("schedules", is_editable); + frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); + frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable); + }, + + make_purchase_invoice: function(frm) { + frappe.call({ + args: { + "asset": frm.doc.name, + "item_code": frm.doc.item_code, + "gross_purchase_amount": frm.doc.gross_purchase_amount, + "company": frm.doc.company, + "posting_date": frm.doc.purchase_date + }, + method: "erpnext.assets.doctype.asset.asset.make_purchase_invoice", + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }) + }, + + create_asset_maintenance: function(frm) { + frappe.call({ + args: { + "asset": frm.doc.name, + "item_code": frm.doc.item_code, + "item_name": frm.doc.item_name, + "asset_category": frm.doc.asset_category, + "company": frm.doc.company + }, + method: "erpnext.assets.doctype.asset.asset.create_asset_maintenance", + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }) + } +}); + +frappe.ui.form.on('Depreciation Schedule', { + make_depreciation_entry: function(frm, cdt, cdn) { + var row = locals[cdt][cdn]; + if (!row.journal_entry) { + frappe.call({ + method: "erpnext.assets.doctype.asset.depreciation.make_depreciation_entry", + args: { + "asset_name": frm.doc.name, + "date": row.schedule_date + }, + callback: function(r) { + frappe.model.sync(r.message); + frm.refresh(); + } + }) + } + }, + + depreciation_amount: function(frm, cdt, cdn) { + erpnext.asset.set_accululated_depreciation(frm); + } + +}) + +erpnext.asset.set_accululated_depreciation = function(frm) { + if(frm.doc.depreciation_method != "Manual") return; + + var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation); + $.each(frm.doc.schedules || [], function(i, row) { + accumulated_depreciation += flt(row.depreciation_amount); + frappe.model.set_value(row.doctype, row.name, + "accumulated_depreciation_amount", accumulated_depreciation); + }) +}; + +erpnext.asset.scrap_asset = function(frm) { + frappe.confirm(__("Do you really want to scrap this asset?"), function () { + frappe.call({ + args: { + "asset_name": frm.doc.name + }, + method: "erpnext.assets.doctype.asset.depreciation.scrap_asset", + callback: function(r) { + cur_frm.reload_doc(); + } + }) + }) +}; + +erpnext.asset.restore_asset = function(frm) { + frappe.confirm(__("Do you really want to restore this scrapped asset?"), function () { + frappe.call({ + args: { + "asset_name": frm.doc.name + }, + method: "erpnext.assets.doctype.asset.depreciation.restore_asset", + callback: function(r) { + cur_frm.reload_doc(); + } + }) + }) +}; + +erpnext.asset.transfer_asset = function(frm) { + var dialog = new frappe.ui.Dialog({ + title: __("Transfer Asset"), + fields: [ + { + "label": __("Target Warehouse"), + "fieldname": "target_warehouse", + "fieldtype": "Link", + "options": "Warehouse", + "get_query": function () { + return { + filters: [ + ["Warehouse", "company", "in", ["", cstr(frm.doc.company)]], + ["Warehouse", "is_group", "=", 0] + ] + } + }, + "reqd": 1 + }, + { + "label": __("Date"), + "fieldname": "transfer_date", + "fieldtype": "Datetime", + "reqd": 1, + "default": frappe.datetime.now_datetime() + } + ] + }); + + dialog.set_primary_action(__("Transfer"), function() { + var args = dialog.get_values(); + if(!args) return; + dialog.hide(); + return frappe.call({ + type: "GET", + method: "erpnext.assets.doctype.asset.asset.transfer_asset", + args: { + args: { + "asset": frm.doc.name, + "transaction_date": args.transfer_date, + "source_warehouse": frm.doc.warehouse, + "target_warehouse": args.target_warehouse, + "company": frm.doc.company + } + }, + freeze: true, + callback: function(r) { + cur_frm.reload_doc(); + } + }) + }); + dialog.show(); +}; \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json new file mode 100644 index 0000000000..e0ed56acc5 --- /dev/null +++ b/erpnext/assets/doctype/asset/asset.json @@ -0,0 +1,1228 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:asset_name", + "beta": 0, + "creation": "2016-03-01 17:01:27.920130", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Asset Name", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_code", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Item Code", + "length": 0, + "no_copy": 0, + "options": "Item", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_name", + "fieldtype": "Read Only", + "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": "Item Name", + "length": 0, + "no_copy": 0, + "options": "item_code.item_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_category", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Asset Category", + "length": 0, + "no_copy": 0, + "options": "Asset Category", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 1, + "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Repair\nOut of Order", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_owner", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Asset Owner", + "length": 0, + "no_copy": 0, + "options": "Company\nSupplier\nCustomer", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.asset_owner == \"Supplier\"", + "fieldname": "supplier", + "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": "Supplier", + "length": 0, + "no_copy": 0, + "options": "Supplier", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.asset_owner == \"Customer\"", + "fieldname": "customer", + "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": "Customer", + "length": 0, + "no_copy": 0, + "options": "Customer", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Image", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "purchase_date", + "fieldtype": "Date", + "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": "Purchase Date", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "purchase_invoice", + "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": "Purchase Invoice", + "length": 0, + "no_copy": 1, + "options": "Purchase Invoice", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_existing_asset", + "fieldtype": "Check", + "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": "Is Existing Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "disposal_date", + "fieldtype": "Date", + "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": "Disposal Date", + "length": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "journal_entry_for_scrap", + "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": "Journal Entry for Scrap", + "length": 0, + "no_copy": 1, + "options": "Journal Entry", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "gross_purchase_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Gross Purchase Amount", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "expected_value_after_useful_life", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Expected Value After Useful Life", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "is_existing_asset", + "fieldname": "opening_accumulated_depreciation", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Opening Accumulated Depreciation", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "calculate_depreciation", + "fieldtype": "Check", + "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": "Calculate Depreciation", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "calculate_depreciation", + "fieldname": "section_break_23", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Depreciation", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "", + "fieldname": "depreciation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Depreciation Method", + "length": 0, + "no_copy": 0, + "options": "\nStraight Line\nDouble Declining Balance\nManual", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "value_after_depreciation", + "fieldtype": "Currency", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Value After Depreciation", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_number_of_depreciations", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Number of Depreciations", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_24", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)", + "fieldname": "number_of_depreciations_booked", + "fieldtype": "Int", + "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": "Number of Depreciations Booked", + "length": 0, + "no_copy": 1, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frequency_of_depreciation", + "fieldtype": "Int", + "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": "Frequency of Depreciation (Months)", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "next_depreciation_date", + "fieldtype": "Date", + "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": "Next Depreciation Date", + "length": 0, + "no_copy": 1, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "calculate_depreciation", + "fieldname": "section_break_14", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Depreciation Schedule", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "schedules", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Depreciation Schedules", + "length": 0, + "no_copy": 1, + "options": "Depreciation Schedule", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_31", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maintenance", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Check if Asset requires Preventive Maintenance or Calibration", + "fieldname": "maintenance_required", + "fieldtype": "Check", + "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": "Maintenance Required", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "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", + "length": 0, + "no_copy": 1, + "options": "Asset", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 72, + "image_field": "image", + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-11-21 04:45:24.039059", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 1, + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py new file mode 100644 index 0000000000..97cb876930 --- /dev/null +++ b/erpnext/assets/doctype/asset/asset.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, add_months, cint, nowdate, getdate +from frappe.model.document import Document +from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account +from erpnext.assets.doctype.asset.depreciation \ + import get_disposal_account_and_cost_center, get_depreciation_accounts + +class Asset(Document): + def validate(self): + self.status = self.get_status() + self.validate_item() + self.set_missing_values() + self.validate_asset_values() + self.make_depreciation_schedule() + self.set_accumulated_depreciation() + if self.get("schedules"): + self.validate_expected_value_after_useful_life() + # Validate depreciation related accounts + get_depreciation_accounts(self) + + def on_submit(self): + self.set_status() + + def on_cancel(self): + self.validate_cancellation() + self.delete_depreciation_entries() + self.set_status() + + def validate_item(self): + item = frappe.db.get_value("Item", self.item_code, + ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1) + if not item: + frappe.throw(_("Item {0} does not exist").format(self.item_code)) + elif item.disabled: + frappe.throw(_("Item {0} has been disabled").format(self.item_code)) + elif not item.is_fixed_asset: + frappe.throw(_("Item {0} must be a Fixed Asset Item").format(self.item_code)) + elif item.is_stock_item: + frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code)) + + def set_missing_values(self): + if self.item_code: + item_details = get_item_details(self.item_code) + for field, value in item_details.items(): + if not self.get(field): + self.set(field, value) + + self.value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) + + def validate_asset_values(self): + if flt(self.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): + frappe.throw(_("Expected Value After Useful Life must be less than Gross Purchase Amount")) + + if not flt(self.gross_purchase_amount): + frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) + + if not self.is_existing_asset and self.calculate_depreciation: + self.opening_accumulated_depreciation = 0 + self.number_of_depreciations_booked = 0 + if not self.next_depreciation_date: + frappe.throw(_("Next Depreciation Date is mandatory for new asset")) + else: + depreciable_amount = flt(self.gross_purchase_amount) - flt(self.expected_value_after_useful_life) + if flt(self.opening_accumulated_depreciation) > depreciable_amount: + frappe.throw(_("Opening Accumulated Depreciation must be less than equal to {0}") + .format(depreciable_amount)) + + if self.opening_accumulated_depreciation: + if not self.number_of_depreciations_booked: + frappe.throw(_("Please set Number of Depreciations Booked")) + else: + self.number_of_depreciations_booked = 0 + + if cint(self.number_of_depreciations_booked) > cint(self.total_number_of_depreciations): + frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) + + if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(nowdate()): + frappe.msgprint(_("Next Depreciation Date is entered as past date"), title=_('Warning'), indicator='red') + + if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.purchase_date): + frappe.throw(_("Next Depreciation Date cannot be before Purchase Date")) + + if (flt(self.value_after_depreciation) > flt(self.expected_value_after_useful_life) + and not self.next_depreciation_date and self.calculate_depreciation): + frappe.throw(_("Please set Next Depreciation Date")) + + def make_depreciation_schedule(self): + if self.depreciation_method != 'Manual': + self.schedules = [] + + if not self.get("schedules") and self.next_depreciation_date: + value_after_depreciation = flt(self.value_after_depreciation) + + number_of_pending_depreciations = cint(self.total_number_of_depreciations) - \ + cint(self.number_of_depreciations_booked) + if number_of_pending_depreciations: + for n in xrange(number_of_pending_depreciations): + schedule_date = add_months(self.next_depreciation_date, + n * cint(self.frequency_of_depreciation)) + + depreciation_amount = self.get_depreciation_amount(value_after_depreciation) + value_after_depreciation -= flt(depreciation_amount) + + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount + }) + + def set_accumulated_depreciation(self): + accumulated_depreciation = flt(self.opening_accumulated_depreciation) + value_after_depreciation = flt(self.value_after_depreciation) + for i, d in enumerate(self.get("schedules")): + depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount")) + value_after_depreciation -= flt(depreciation_amount) + + if i==len(self.get("schedules"))-1 and self.depreciation_method == "Straight Line": + depreciation_amount += flt(value_after_depreciation - flt(self.expected_value_after_useful_life), + d.precision("depreciation_amount")) + + d.depreciation_amount = depreciation_amount + accumulated_depreciation += d.depreciation_amount + d.accumulated_depreciation_amount = flt(accumulated_depreciation, d.precision("accumulated_depreciation_amount")) + + def get_depreciation_amount(self, depreciable_value): + if self.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(self.value_after_depreciation) - + flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - + cint(self.number_of_depreciations_booked)) + else: + factor = 200.0 / self.total_number_of_depreciations + depreciation_amount = flt(depreciable_value * factor / 100, 0) + + value_after_depreciation = flt(depreciable_value) - depreciation_amount + if value_after_depreciation < flt(self.expected_value_after_useful_life): + depreciation_amount = flt(depreciable_value) - flt(self.expected_value_after_useful_life) + + return depreciation_amount + + def validate_expected_value_after_useful_life(self): + accumulated_depreciation_after_full_schedule = \ + max([d.accumulated_depreciation_amount for d in self.get("schedules")]) + + asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule), + self.precision('expected_value_after_useful_life')) + + if self.expected_value_after_useful_life < asset_value_after_full_schedule: + frappe.throw(_("Expected value after useful life must be greater than or equal to {0}") + .format(asset_value_after_full_schedule)) + + def validate_cancellation(self): + if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): + frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) + + if self.purchase_invoice: + frappe.throw(_("Please cancel Purchase Invoice {0} first").format(self.purchase_invoice)) + + def delete_depreciation_entries(self): + for d in self.get("schedules"): + if d.journal_entry: + frappe.get_doc("Journal Entry", d.journal_entry).cancel() + d.db_set("journal_entry", None) + + self.db_set("value_after_depreciation", + (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation))) + + def set_status(self, status=None): + '''Get and update status''' + if not status: + status = self.get_status() + self.db_set("status", status) + + def get_status(self): + '''Returns status based on whether it is draft, submitted, scrapped or depreciated''' + if self.docstatus == 0: + status = "Draft" + elif self.docstatus == 1: + status = "Submitted" + if self.journal_entry_for_scrap: + status = "Scrapped" + elif flt(self.value_after_depreciation) <= flt(self.expected_value_after_useful_life): + status = "Fully Depreciated" + elif flt(self.value_after_depreciation) < flt(self.gross_purchase_amount): + status = 'Partially Depreciated' + elif self.docstatus == 2: + status = "Cancelled" + return status + +@frappe.whitelist() +def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, posting_date): + pi = frappe.new_doc("Purchase Invoice") + pi.company = company + pi.currency = frappe.db.get_value("Company", company, "default_currency") + pi.set_posting_time = 1 + pi.posting_date = posting_date + pi.append("items", { + "item_code": item_code, + "is_fixed_asset": 1, + "asset": asset, + "expense_account": get_fixed_asset_account(asset), + "qty": 1, + "price_list_rate": gross_purchase_amount, + "rate": gross_purchase_amount + }) + pi.set_missing_values() + return pi + +@frappe.whitelist() +def make_sales_invoice(asset, item_code, company): + si = frappe.new_doc("Sales Invoice") + si.company = company + si.currency = frappe.db.get_value("Company", company, "default_currency") + disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(company) + si.append("items", { + "item_code": item_code, + "is_fixed_asset": 1, + "asset": asset, + "income_account": disposal_account, + "cost_center": depreciation_cost_center, + "qty": 1 + }) + si.set_missing_values() + return si + +@frappe.whitelist() +def create_asset_maintenance(asset, item_code, item_name, asset_category, company): + asset_maintenance = frappe.new_doc("Asset Maintenance") + asset_maintenance.update({ + "asset_name": asset, + "company": company, + "item_code": item_code, + "item_name": item_name, + "asset_category": asset_category + }) + return asset_maintenance + +@frappe.whitelist() +def transfer_asset(args): + import json + args = json.loads(args) + movement_entry = frappe.new_doc("Asset Movement") + movement_entry.update(args) + movement_entry.insert() + movement_entry.submit() + + frappe.db.commit() + + frappe.msgprint(_("Asset Movement record {0} created").format("{0}".format(movement_entry.name))) + +@frappe.whitelist() +def get_item_details(item_code): + asset_category = frappe.db.get_value("Item", item_code, "asset_category") + + if not asset_category: + frappe.throw(_("Please enter Asset Category in Item {0}").format(item_code)) + + ret = frappe.db.get_value("Asset Category", asset_category, + ["depreciation_method", "total_number_of_depreciations", "frequency_of_depreciation"], as_dict=1) + + ret.update({ + "asset_category": asset_category + }) + + return ret diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py new file mode 100644 index 0000000000..94dad1b311 --- /dev/null +++ b/erpnext/assets/doctype/asset/asset_dashboard.py @@ -0,0 +1,14 @@ +def get_data(): + return { + 'fieldname': 'asset_name', + 'transactions': [ + { + 'label': ['Maintenance'], + 'items': ['Asset Maintenance', 'Asset Maintenance Log'] + }, + { + 'label': ['Repair'], + 'items': ['Asset Repair'] + } + ] + } \ No newline at end of file diff --git a/erpnext/accounts/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js similarity index 100% rename from erpnext/accounts/doctype/asset/asset_list.js rename to erpnext/assets/doctype/asset/asset_list.js diff --git a/erpnext/accounts/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py similarity index 100% rename from erpnext/accounts/doctype/asset/depreciation.py rename to erpnext/assets/doctype/asset/depreciation.py diff --git a/erpnext/assets/doctype/asset/test_asset.js b/erpnext/assets/doctype/asset/test_asset.js new file mode 100644 index 0000000000..6119e38217 --- /dev/null +++ b/erpnext/assets/doctype/asset/test_asset.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset + () => frappe.tests.make('Asset', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py new file mode 100644 index 0000000000..9c3ed80b72 --- /dev/null +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -0,0 +1,343 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from frappe.utils import cstr, nowdate, getdate, flt +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset +from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase_invoice + +class TestAsset(unittest.TestCase): + def setUp(self): + set_depreciation_settings_in_company() + create_asset() + frappe.db.sql("delete from `tabTax Rule`") + + def test_purchase_asset(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.submit() + + pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, + asset.company, asset.purchase_date) + pi.supplier = "_Test Supplier" + pi.insert() + pi.submit() + + asset.load_from_db() + self.assertEqual(asset.supplier, "_Test Supplier") + self.assertEqual(asset.purchase_date, getdate("2015-01-01")) + self.assertEqual(asset.purchase_invoice, pi.name) + + expected_gle = ( + ("_Test Fixed Asset - _TC", 100000.0, 0.0), + ("Creditors - _TC", 0.0, 100000.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Purchase Invoice' and voucher_no = %s + order by account""", pi.name) + + self.assertEqual(gle, expected_gle) + + pi.cancel() + + asset.load_from_db() + self.assertEqual(asset.supplier, None) + self.assertEqual(asset.purchase_invoice, None) + + self.assertFalse(frappe.db.get_value("GL Entry", + {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) + + + def test_schedule_for_straight_line_method(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2020-12-31", 30000, 30000], + ["2021-03-31", 30000, 60000], + ["2021-06-30", 30000, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_straight_line_method_for_existing_asset(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.is_existing_asset = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 40000 + asset.save() + + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2020-12-31", 25000, 65000], + ["2021-03-31", 25000, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + + def test_schedule_for_double_declining_method(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.depreciation_method = "Double Declining Balance" + asset.save() + + expected_schedules = [ + ["2020-12-31", 66667, 66667], + ["2021-03-31", 22222, 88889], + ["2021-06-30", 1111, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_double_declining_method_for_existing_asset(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.depreciation_method = "Double Declining Balance" + asset.is_existing_asset = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 50000 + asset.save() + + expected_schedules = [ + ["2020-12-31", 33333, 83333], + ["2021-03-31", 6667, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_manual_method(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.depreciation_method = "Manual" + asset.schedules = [] + for schedule_date, amount in [["2020-12-31", 40000], ["2021-06-30", 30000], ["2021-10-31", 20000]]: + asset.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": amount + }) + asset.save() + + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2020-12-31", 40000, 40000], + ["2021-06-30", 30000, 70000], + ["2021-10-31", 20000, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_depreciation(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.submit() + asset.load_from_db() + self.assertEqual(asset.status, "Submitted") + + frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") + + post_depreciation_entries(date="2021-01-01") + asset.load_from_db() + + self.assertEqual(asset.status, "Partially Depreciated") + + # check depreciation entry series + self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), + ("_Test Depreciations - _TC", 30000.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where against_voucher_type='Asset' and against_voucher = %s + order by account""", asset.name) + + self.assertEqual(gle, expected_gle) + self.assertEqual(asset.get("value_after_depreciation"), 70000) + + def test_depreciation_entry_cancellation(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.submit() + post_depreciation_entries(date="2021-01-01") + + asset.load_from_db() + + # cancel depreciation entry + depr_entry = asset.get("schedules")[0].journal_entry + self.assertTrue(depr_entry) + frappe.get_doc("Journal Entry", depr_entry).cancel() + + asset.load_from_db() + depr_entry = asset.get("schedules")[0].journal_entry + self.assertFalse(depr_entry) + + + def test_scrap_asset(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.submit() + post_depreciation_entries(date="2021-01-01") + + scrap_asset("Macbook Pro 1") + + asset.load_from_db() + self.assertEqual(asset.status, "Scrapped") + self.assertTrue(asset.journal_entry_for_scrap) + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), + ("_Test Fixed Asset - _TC", 0.0, 100000.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Journal Entry' and voucher_no = %s + order by account""", asset.journal_entry_for_scrap) + + self.assertEqual(gle, expected_gle) + + restore_asset("Macbook Pro 1") + + asset.load_from_db() + self.assertFalse(asset.journal_entry_for_scrap) + self.assertEqual(asset.status, "Partially Depreciated") + + def test_asset_sale(self): + frappe.get_doc("Asset", "Macbook Pro 1").submit() + post_depreciation_entries(date="2021-01-01") + + si = make_sales_invoice(asset="Macbook Pro 1", item_code="Macbook Pro", company="_Test Company") + si.customer = "_Test Customer" + si.due_date = nowdate() + si.get("items")[0].rate = 25000 + si.insert() + si.submit() + + self.assertEqual(frappe.db.get_value("Asset", "Macbook Pro 1", "status"), "Sold") + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), + ("_Test Fixed Asset - _TC", 0.0, 100000.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 45000.0, 0.0), + ("Debtors - _TC", 25000.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no = %s + order by account""", si.name) + + self.assertEqual(gle, expected_gle) + + si.cancel() + + self.assertEqual(frappe.db.get_value("Asset", "Macbook Pro 1", "status"), "Partially Depreciated") + + def test_asset_expected_value_after_useful_life(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.depreciation_method = "Straight Line" + asset.is_existing_asset = 1 + asset.total_number_of_depreciations = 400 + asset.gross_purchase_amount = 16866177.00 + asset.expected_value_after_useful_life = 500000 + asset.save() + + accumulated_depreciation_after_full_schedule = \ + max([d.accumulated_depreciation_amount for d in asset.get("schedules")]) + + asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule)) + + self.assertTrue(asset.expected_value_after_useful_life >= asset_value_after_full_schedule) + + def tearDown(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + + if asset.docstatus == 1 and asset.status not in ("Scrapped", "Sold", "Draft", "Cancelled"): + asset.cancel() + + self.assertEqual(frappe.db.get_value("Asset", "Macbook Pro 1", "status"), "Cancelled") + + frappe.delete_doc("Asset", "Macbook Pro 1") + +def create_asset(): + if not frappe.db.exists("Asset Category", "Computers"): + create_asset_category() + + if not frappe.db.exists("Item", "Macbook Pro"): + create_fixed_asset_item() + + asset = frappe.get_doc({ + "doctype": "Asset", + "asset_name": "Macbook Pro 1", + "asset_category": "Computers", + "item_code": "Macbook Pro", + "company": "_Test Company", + "purchase_date": "2015-01-01", + "next_depreciation_date": "2020-12-31", + "gross_purchase_amount": 100000, + "expected_value_after_useful_life": 10000, + "warehouse": "_Test Warehouse - _TC" + }) + try: + asset.save() + except frappe.DuplicateEntryError: + pass + + return asset + +def create_asset_category(): + asset_category = frappe.new_doc("Asset Category") + asset_category.asset_category_name = "Computers" + asset_category.total_number_of_depreciations = 3 + asset_category.frequency_of_depreciation = 3 + asset_category.append("accounts", { + "company_name": "_Test Company", + "fixed_asset_account": "_Test Fixed Asset - _TC", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", + "depreciation_expense_account": "_Test Depreciations - _TC" + }) + asset_category.insert() + +def create_fixed_asset_item(): + try: + frappe.get_doc({ + "doctype": "Item", + "item_code": "Macbook Pro", + "item_name": "Macbook Pro", + "description": "Macbook Pro Retina Display", + "asset_category": "Computers", + "item_group": "All Item Groups", + "stock_uom": "Nos", + "is_stock_item": 0, + "is_fixed_asset": 1 + }).insert() + except frappe.DuplicateEntryError: + pass + +def set_depreciation_settings_in_company(): + company = frappe.get_doc("Company", "_Test Company") + company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC" + company.depreciation_expense_account = "_Test Depreciations - _TC" + company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" + company.depreciation_cost_center = "_Test Cost Center - _TC" + company.save() + + # Enable booking asset depreciation entry automatically + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category/__init__.py b/erpnext/assets/doctype/asset_category/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js new file mode 100644 index 0000000000..aafe8a69a0 --- /dev/null +++ b/erpnext/assets/doctype/asset_category/asset_category.js @@ -0,0 +1,44 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Category', { + onload: function(frm) { + frm.add_fetch('company_name', 'accumulated_depreciation_account', 'accumulated_depreciation_account'); + frm.add_fetch('company_name', 'depreciation_expense_account', 'accumulated_depreciation_account'); + + frm.set_query('fixed_asset_account', 'accounts', function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + "filters": { + "account_type": "Fixed Asset", + "root_type": "Asset", + "is_group": 0, + "company": d.company_name + } + }; + }); + + frm.set_query('accumulated_depreciation_account', 'accounts', function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + "filters": { + "root_type": "Asset", + "is_group": 0, + "company": d.company_name + } + }; + }); + + frm.set_query('depreciation_expense_account', 'accounts', function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + "filters": { + "root_type": "Expense", + "is_group": 0, + "company": d.company_name + } + }; + }); + + } +}); \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json new file mode 100644 index 0000000000..39b3978383 --- /dev/null +++ b/erpnext/assets/doctype/asset_category/asset_category.json @@ -0,0 +1,295 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:asset_category_name", + "beta": 0, + "creation": "2016-03-01 17:41:39.778765", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_category_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Asset Category Name", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Straight Line", + "fieldname": "depreciation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Depreciation Method", + "length": 0, + "no_copy": 0, + "options": "\nStraight Line\nDouble Declining Balance", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_number_of_depreciations", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Total Number of Depreciations", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frequency_of_depreciation", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Frequency of Depreciation (Months)", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Accounts", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "accounts", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Accounts", + "length": 0, + "no_copy": 0, + "options": "Asset Category Account", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-19 16:08:02.114363", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Category", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py new file mode 100644 index 0000000000..5279c37ea8 --- /dev/null +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import cint +from frappe.model.document import Document + +class AssetCategory(Document): + def validate(self): + for field in ("total_number_of_depreciations", "frequency_of_depreciation"): + if cint(self.get(field))<1: + frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.js b/erpnext/assets/doctype/asset_category/test_asset_category.js new file mode 100644 index 0000000000..7e343b7519 --- /dev/null +++ b/erpnext/assets/doctype/asset_category/test_asset_category.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Category", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Category + () => frappe.tests.make('Asset Category', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py new file mode 100644 index 0000000000..b32f9b5020 --- /dev/null +++ b/erpnext/assets/doctype/asset_category/test_asset_category.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestAssetCategory(unittest.TestCase): + def test_mandatory_fields(self): + asset_category = frappe.new_doc("Asset Category") + asset_category.asset_category_name = "Computers" + + self.assertRaises(frappe.MandatoryError, asset_category.insert) + + asset_category.total_number_of_depreciations = 3 + asset_category.frequency_of_depreciation = 3 + asset_category.append("accounts", { + "company_name": "_Test Company", + "fixed_asset_account": "_Test Fixed Asset - _TC", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", + "depreciation_expense_account": "_Test Depreciations - _TC" + }) + + try: + asset_category.insert() + except frappe.DuplicateEntryError: + pass + \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance/__init__.py b/erpnext/assets/doctype/asset_maintenance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js new file mode 100644 index 0000000000..6a6e72f546 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js @@ -0,0 +1,105 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Maintenance', { + setup: (frm) => { + frm.fields_dict["asset_maintenance_tasks"].grid.get_field("assign_to").get_query = function(){ + return { + filters: { + parent: frm.doc.maintenance_team + } + }; + }, + frm.set_indicator_formatter('maintenance_status', + function(doc) { + let indicator = 'blue'; + if (doc.maintenance_status == 'Overdue') { + indicator = 'orange'; + } + if (doc.maintenance_status == 'Cancelled') { + indicator = 'red'; + } + return indicator; + } + ); + }, + refresh: (frm) => { + if(!frm.is_new()) { + frm.trigger('make_dashboard'); + } + }, + make_dashboard: (frm) => { + if(!frm.is_new()) { + frappe.call({ + method: 'erpnext.assets.doctype.asset_maintenance.asset_maintenance.get_maintenance_log', + args: {asset_name: frm.doc.asset_name}, + callback: (r) => { + if(!r.message) { + return; + } + var section = frm.dashboard.add_section(`
+ ${ __("Maintenance Log") }
`); + var rows = $('
').appendTo(section); + // show + (r.message || []).forEach(function(d) { + $(`
+ +
`).appendTo(rows); + }); + frm.dashboard.show(); + } + }); + } + } +}); + +frappe.ui.form.on('Asset Maintenance Task', { + start_date: (frm, cdt, cdn) => { + get_next_due_date(frm, cdt, cdn); + }, + periodicity: (frm, cdt, cdn) => { + get_next_due_date(frm, cdt, cdn); + }, + last_completion_date: (frm, cdt, cdn) => { + get_next_due_date(frm, cdt, cdn); + }, + end_date: (frm, cdt, cdn) => { + get_next_due_date(frm, cdt, cdn); + }, + assign_to: (frm, cdt, cdn) => { + if (frm.doc.__islocal) { + frappe.model.set_value(cdt, cdn, "assign_to", ""); + frappe.model.set_value(cdt, cdn, "assign_to_name", ""); + frappe.throw(__("Please save before assigning task.")); + } + } +}); + +var get_next_due_date = function (frm, cdt, cdn) { + var d = locals[cdt][cdn]; + if (d.start_date && d.periodicity) { + return frappe.call({ + method: 'erpnext.assets.doctype.asset_maintenance.asset_maintenance.calculate_next_due_date', + args: { + start_date: d.start_date, + periodicity: d.periodicity, + end_date: d.end_date, + last_completion_date: d.last_completion_date, + next_due_date: d.next_due_date + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "next_due_date", r.message); + } + else { + frappe.model.set_value(cdt, cdn, "next_due_date", ""); + } + } + }); + } +}; \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json new file mode 100644 index 0000000000..f526bb8d72 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json @@ -0,0 +1,459 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:asset_name", + "beta": 0, + "creation": "2017-10-19 16:50:22.879545", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_name", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Asset Name", + "length": 0, + "no_copy": 0, + "options": "Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_category", + "fieldtype": "Read Only", + "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": "Asset Category", + "length": 0, + "no_copy": 0, + "options": "asset_name.asset_category", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_code", + "fieldtype": "Read Only", + "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": "Item Code", + "length": 0, + "no_copy": 0, + "options": "asset_name.item_code", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_name", + "fieldtype": "Read Only", + "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": "Item Name", + "length": 0, + "no_copy": 0, + "options": "asset_name.item_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_team", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Maintenance Team", + "length": 0, + "no_copy": 0, + "options": "Asset Maintenance Team", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_9", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_manager", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maintenance Manager", + "length": 0, + "no_copy": 0, + "options": "maintenance_team.maintenance_manager", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_manager_name", + "fieldtype": "Read Only", + "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": "Maintenance Manager Name", + "length": 0, + "no_copy": 0, + "options": "maintenance_team.maintenance_manager_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Tasks", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_maintenance_tasks", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maintenance Tasks", + "length": 0, + "no_copy": 0, + "options": "Asset Maintenance Task", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-11-21 04:42:54.893169", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py new file mode 100644 index 0000000000..0ddb5d1687 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.desk.form import assign_to +from frappe import throw, _ +from frappe.utils import add_days, add_months, add_years, getdate, nowdate + +class AssetMaintenance(Document): + def validate(self): + for task in self.get('asset_maintenance_tasks'): + if task.end_date and (getdate(task.start_date) >= getdate(task.end_date)): + throw(_("Start date should be less than end date for task {0}").format(task.maintenance_task)) + if getdate(task.next_due_date) < getdate(nowdate()): + task.maintenance_status = "Overdue" + if not self.get("__islocal"): + if not task.assign_to: + task.assign_to = self.maintenance_manager + if task.assign_to: + self.assign_tasks(task) + + def assign_tasks(self, task): + team_member = frappe.get_doc('User', task.assign_to).email + args = { + 'doctype' : self.doctype, + 'assign_to' : team_member, + 'name' : self.name, + 'description' : task.maintenance_task, + 'date' : task.next_due_date + } + if not frappe.db.sql("""select owner from `tabToDo` + where reference_type=%(doctype)s and reference_name=%(name)s and status="Open" + and owner=%(assign_to)s""", args): + assign_to.add(args) + + +@frappe.whitelist() +def calculate_next_due_date(periodicity, start_date = None, end_date = None, last_completion_date = None, next_due_date = None): + if not start_date and not last_completion_date: + start_date = frappe.utils.now() + + if last_completion_date and (last_completion_date > start_date or not start_date): + start_date = last_completion_date + + if periodicity == 'Daily': + next_due_date = add_days(start_date, 1) + if periodicity == 'Weekly': + next_due_date = add_days(start_date, 7) + if periodicity == 'Monthly': + next_due_date = add_months(start_date, 1) + if periodicity == 'Yearly': + next_due_date = add_years(start_date, 1) + if periodicity == '2 Yearly': + next_due_date = add_years(start_date, 2) + if periodicity == 'Quarterly': + next_due_date = add_months(start_date, 3) + if end_date and (start_date >= end_date or last_completion_date >= end_date or next_due_date): + next_due_date = "" + return next_due_date + +@frappe.whitelist() +def get_maintenance_log(asset_name): + return frappe.db.sql(""" + select maintenance_status, count(asset_name) as count, asset_name + from `tabAsset Maintenance Log` + where asset_name=%s group by maintenance_status""", + (asset_name), as_dict=1) + \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js new file mode 100644 index 0000000000..f9b38a1020 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Maintenance", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Maintenance + () => frappe.tests.make('Asset Maintenance', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py new file mode 100644 index 0000000000..139d20210a --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from frappe.utils import nowdate, add_days +from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate_next_due_date + +class TestAssetMaintenance(unittest.TestCase): + def setUp(self): + set_depreciation_settings_in_company() + create_asset() + create_maintenance_team() + + def test_create_asset_maintenance(self): + if not frappe.db.exists("Asset Maintenance", "Photocopier"): + asset_maintenance = frappe.get_doc({ + "doctype": "Asset Maintenance", + "asset_name": "Photocopier", + "maintenance_team": "Team Awesome", + "company": "_Test Company", + "asset_maintenance_tasks": get_maintenance_tasks() + }).insert() + + next_due_date = calculate_next_due_date(nowdate(), "Monthly") + self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) + + def test_create_asset_maintenance_log(self): + if not frappe.db.exists("Asset Maintenance Log", "Photocopier"): + asset_maintenance_log = frappe.get_doc({ + "doctype": "Asset Maintenance Log", + "asset_maintenance": "Photocopier", + "task": "Change Oil", + "completion_date": add_days(nowdate(), 2), + "maintenance_status": "Completed" + }).insert() + asset_maintenance = frappe.get_doc("Asset Maintenance", "Photocopier") + next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly") + self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) + +def create_asset(): + if not frappe.db.exists("Asset Category", "Equipment"): + create_asset_category() + + if not frappe.db.exists("Item", "Photocopier"): + frappe.get_doc({ + "doctype": "Item", + "item_code": "Photocopier", + "item_name": "Photocopier", + "item_group": "All Item Groups", + "company": "_Test Company", + "is_fixed_asset": 1, + "is_stock_item": 0, + "asset_category": "Equipment" + }).insert() + + if not frappe.db.exists("Asset", "Photocopier"): + frappe.get_doc({ + "doctype": "Asset", + "asset_name": "Photocopier", + "item_code": "Photocopier", + "asset_category": "Equipment", + "gross_purchase_amount": 100000, + "expected_value_after_useful_life": 10000, + "warehouse": "_Test Warehouse - _TC", + "company": "_Test Company", + "purchase_date": nowdate(), + "maintenance_required": 1 + }).insert() + +def create_maintenance_team(): + user_list = ["marcus@abc.com", "thalia@abc.com", "mathias@abc.com"] + if not frappe.db.exists("Role", "Technician"): + frappe.get_doc({"doctype": "Role", "role_name": "Technician"}).insert() + for user in user_list: + if not frappe.db.get_value("User", user): + frappe.get_doc({ + "doctype": "User", + "email": user, + "first_name": user, + "new_password": "password", + "roles": [{"doctype": "Has Role", "role": "Technician"}] + }).insert() + + if not frappe.db.exists("Asset Maintenance Team", "Team Awesome"): + frappe.get_doc({ + "doctype": "Asset Maintenance Team", + "maintenance_team_name": "Team Awesome", + "company": "_Test Company", + "maintenance_team_members": get_maintenance_team(user_list) + }).insert() + +def get_maintenance_team(user_list): + return [{"team_member": user_list[0], + "full_name": user_list[0], + "maintenance_role": "Technician" + } + ] + +def get_maintenance_tasks(): + return [{"maintenance_task": "Change Oil", + "start_date": nowdate(), + "periodicity": "Monthly", + "maintenance_type": "Preventive Maintenance", + "maintenance_status": "Planned" + }, + {"maintenance_task": "Check Gears", + "start_date": nowdate(), + "periodicity": "Yearly", + "maintenance_type": "Calibration", + "maintenance_status": "Planned" + } + ] + +def create_asset_category(): + asset_category = frappe.new_doc("Asset Category") + asset_category.asset_category_name = "Equipment" + asset_category.total_number_of_depreciations = 3 + asset_category.frequency_of_depreciation = 3 + asset_category.append("accounts", { + "company_name": "_Test Company", + "fixed_asset_account": "_Test Fixed Asset - _TC", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", + "depreciation_expense_account": "_Test Depreciations - _TC" + }) + asset_category.insert() + +def set_depreciation_settings_in_company(): + company = frappe.get_doc("Company", "_Test Company") + company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC" + company.depreciation_expense_account = "_Test Depreciations - _TC" + company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" + company.depreciation_cost_center = "_Test Cost Center - _TC" + company.save() + + # Enable booking asset depreciation entry automatically + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance_log/__init__.py b/erpnext/assets/doctype/asset_maintenance_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js new file mode 100644 index 0000000000..c5db90ad37 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js @@ -0,0 +1,15 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Maintenance Log', { + asset_maintenance: (frm) => { + frm.set_query('task', function(doc) { + return { + query: "erpnext.assets.doctype.asset_maintenance_log.asset_maintenance_log.get_maintenance_tasks", + filters: { + 'asset_maintenance': doc.asset_maintenance + } + }; + }); + } +}); \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json new file mode 100644 index 0000000000..61ce35af7f --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json @@ -0,0 +1,763 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2017-10-23 16:58:44.424309", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_maintenance", + "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": "Asset Maintenance", + "length": 0, + "no_copy": 0, + "options": "Asset Maintenance", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 0, + "options": "AML-", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_name", + "fieldtype": "Read Only", + "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": "Asset Name", + "length": 0, + "no_copy": 0, + "options": "asset_maintenance.asset_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_code", + "fieldtype": "Read Only", + "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": "Item Code", + "length": 0, + "no_copy": 0, + "options": "asset_maintenance.item_code", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_name", + "fieldtype": "Read Only", + "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": "Item Name", + "length": 0, + "no_copy": 0, + "options": "asset_maintenance.item_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "task", + "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": "Task", + "length": 0, + "no_copy": 0, + "options": "Asset Maintenance Task", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_type", + "fieldtype": "Read Only", + "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": "Maintenance Type", + "length": 0, + "no_copy": 0, + "options": "task.maintenance_type", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "periodicity", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Periodicity", + "length": 0, + "no_copy": 0, + "options": "task.periodicity", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assign_to_name", + "fieldtype": "Read Only", + "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": "Assign To", + "length": 0, + "no_copy": 0, + "options": "task.assign_to_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_6", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Due Date", + "length": 0, + "no_copy": 0, + "options": "task.next_due_date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "completion_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Completion Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Maintenance Status", + "length": 0, + "no_copy": 0, + "options": "Pending\nCompleted\nCancelled\nOverdue", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "has_certificate", + "fieldtype": "Check", + "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": "Has Certificate ", + "length": 0, + "no_copy": 0, + "options": "task.has_certificate", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.has_certificate", + "fieldname": "certificate_attachement", + "fieldtype": "Attach", + "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": "Certificate", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Read Only", + "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": "Description", + "length": 0, + "no_copy": 0, + "options": "task.description", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_9", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "actions_performed", + "fieldtype": "Text Editor", + "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": "Actions performed", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "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", + "length": 0, + "no_copy": 1, + "options": "Asset Maintenance Log", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-29 08:38:55.707903", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance Log", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py new file mode 100644 index 0000000000..4e50751607 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.utils import nowdate, getdate +from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate_next_due_date + +class AssetMaintenanceLog(Document): + def validate(self): + # if getdate(self.due_date) > getdate(nowdate()): + # self.maintenance_status = "Overdue" + + if self.maintenance_status == "Completed" and not self.completion_date: + frappe.throw(_("Please select Completion Date for Completed Asset Maintenance Log")) + + # if self.maintenance_status != "Completed" and self.completion_date: +# frappe.throw(_("Please select Maintenance Status as Completed or remove Completion Date")) + + def on_submit(self): + if self.maintenance_status not in ['Completed', 'Cancelled']: + frappe.throw(_("Maintenance Status has to be Cancelled or Completed to Submit")) + self.update_maintenance_task() + + def update_maintenance_task(self): + asset_maintenance_doc = frappe.get_doc('Asset Maintenance Task', self.task) + if self.maintenance_status == "Completed": + if asset_maintenance_doc.last_completion_date != self.completion_date: + next_due_date = calculate_next_due_date(periodicity = self.periodicity, last_completion_date = self.completion_date) + asset_maintenance_doc.last_completion_date = self.completion_date + asset_maintenance_doc.next_due_date = next_due_date + asset_maintenance_doc.maintenance_status = "Planned" + asset_maintenance_doc.save() + if self.maintenance_status == "Cancelled": + asset_maintenance_doc.maintenance_status = "Cancelled" + asset_maintenance_doc.save() + + +@frappe.whitelist() +def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters): + asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task') + return asset_maintenance_tasks + +@frappe.whitelist() +def get_events(start, end, filters=None): + """Returns events for Gantt / Calendar view rendering. + + :param start: Start date-time. + :param end: End date-time. + :param filters: Filters (JSON). + """ + from frappe.desk.calendar import get_event_conditions + conditions = get_event_conditions("Asset Maintenance Log", filters) + data = frappe.db.sql("""select name, due_date, due_date, + task, maintenance_status, asset_name from `tabAsset Maintenance Log` + where ((ifnull(due_date, '0000-00-00')!= '0000-00-00') \ + and (due_date <= %(end)s) \ + or ((ifnull(due_date, '0000-00-00')!= '0000-00-00') \ + and due_date >= %(start)s)) + {conditions}""".format(conditions=conditions), { + "start": start, + "end": end + }, as_dict=True, update={"allDay": 0}) + + return data \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_calendar.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_calendar.js new file mode 100644 index 0000000000..a4978849f0 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_calendar.js @@ -0,0 +1,22 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.views.calendar["Asset Maintenance Log"] = { + field_map: { + "start": "due_date", + "end": "due_date", + "id": "name", + "title": "task", + "allDay": "allDay", + "progress": "progress" + }, + filters: [ + { + "fieldtype": "Link", + "fieldname": "asset_name", + "options": "Asset Maintenance", + "label": __("Asset Maintenance") + } + ], + get_events_method: "erpnext.assets.doctype.asset_maintenance_log.asset_maintenance_log.get_events" +}; diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js new file mode 100644 index 0000000000..b854413310 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log_list.js @@ -0,0 +1,14 @@ +frappe.listview_settings['Asset Maintenance Log'] = { + add_fields: ["maintenance_status"], + get_indicator: function(doc) { + if(doc.maintenance_status=="Pending") { + return [__("Pending"), "orange"]; + } else if(doc.maintenance_status=="Completed") { + return [__("Completed"), "green"]; + } else if(doc.maintenance_status=="Cancelled") { + return [__("Cancelled"), "red"]; + } else if(doc.maintenance_status=="Overdue") { + return [__("Overdue"), "red"]; + } + } +}; diff --git a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js new file mode 100644 index 0000000000..4e80184ea7 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Maintenance Log", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Maintenance Log + () => frappe.tests.make('Asset Maintenance Log', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py new file mode 100644 index 0000000000..a1ec8792b2 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestAssetMaintenanceLog(unittest.TestCase): + pass diff --git a/erpnext/assets/doctype/asset_maintenance_task/__init__.py b/erpnext/assets/doctype/asset_maintenance_task/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json new file mode 100644 index 0000000000..7cf0fecbb7 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json @@ -0,0 +1,643 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:maintenance_task", + "beta": 0, + "creation": "2017-10-20 07:10:55.903571", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_task", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Maintenance Task", + "length": 0, + "no_copy": 0, + "options": "", + "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, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maintenance Type", + "length": 0, + "no_copy": 0, + "options": "Preventive Maintenance\nCalibration", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "maintenance_status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Maintenance Status", + "length": 0, + "no_copy": 0, + "options": "Planned\nOverdue\nCancelled", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "start_date", + "fieldtype": "Date", + "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": "Start Date", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "periodicity", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Periodicity", + "length": 0, + "no_copy": 0, + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "end_date", + "fieldtype": "Date", + "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": "End Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "certificate_required", + "fieldtype": "Check", + "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": "Certificate Required", + "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": 1, + "set_only_once": 1, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assign_to", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Assign To", + "length": 0, + "no_copy": 0, + "options": "Maintenance Team Member", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assign_to_name", + "fieldtype": "Read Only", + "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": "Assign to Name", + "length": 0, + "no_copy": 0, + "options": "assign_to.full_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "next_due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Next Due Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "last_completion_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Last Completion Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "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": "Description", + "length": 0, + "no_copy": 0, + "options": "", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-10-25 21:51:38.931436", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance Task", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py new file mode 100644 index 0000000000..2a5666d506 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class AssetMaintenanceTask(Document): + pass diff --git a/erpnext/assets/doctype/asset_maintenance_team/__init__.py b/erpnext/assets/doctype/asset_maintenance_team/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.js b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.js new file mode 100644 index 0000000000..c94e3dbc3c --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Maintenance Team', { + refresh: function() { + + } +}); diff --git a/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json new file mode 100644 index 0000000000..28836b5062 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json @@ -0,0 +1,275 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:maintenance_team_name", + "beta": 0, + "creation": "2017-10-20 11:43:47.712616", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_team_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Maintenance Team Name", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_manager", + "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": "Maintenance Manager", + "length": 0, + "no_copy": 0, + "options": "User", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_manager_name", + "fieldtype": "Read Only", + "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": "Maintenance Manager Name", + "length": 0, + "no_copy": 0, + "options": "maintenance_manager.full_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_team_members", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maintenance Team Members", + "length": 0, + "no_copy": 0, + "options": "Maintenance Team Member", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-25 12:59:19.133235", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance Team", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.py b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.py new file mode 100644 index 0000000000..f741a8fff3 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class AssetMaintenanceTeam(Document): + pass diff --git a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js new file mode 100644 index 0000000000..41bf69623e --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Maintenance Team", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Maintenance Team + () => frappe.tests.make('Asset Maintenance Team', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py new file mode 100644 index 0000000000..a0c0b146f1 --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestAssetMaintenanceTeam(unittest.TestCase): + pass diff --git a/erpnext/assets/doctype/asset_movement/__init__.py b/erpnext/assets/doctype/asset_movement/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js new file mode 100644 index 0000000000..70c4080d1c --- /dev/null +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -0,0 +1,18 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Movement', { + onload: function(frm) { + frm.add_fetch("asset", "warehouse", "source_warehouse"); + + frm.set_query("target_warehouse", function() { + return { + filters: [ + ["Warehouse", "company", "in", ["", cstr(frm.doc.company)]], + ["Warehouse", "is_group", "=", 0] + ] + } + }) + + } +}); diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json new file mode 100644 index 0000000000..0c05552962 --- /dev/null +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -0,0 +1,315 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "AM-.#####", + "beta": 0, + "creation": "2016-04-25 18:00:23.559973", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Asset", + "length": 0, + "no_copy": 0, + "options": "Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "transaction_date", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Transaction Date", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "source_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Source Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "target_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Target Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "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", + "length": 0, + "no_copy": 1, + "options": "Asset Movement", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-19 16:08:17.389257", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Movement", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py new file mode 100644 index 0000000000..574c49992b --- /dev/null +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.model.document import Document + +class AssetMovement(Document): + def validate(self): + self.validate_asset() + self.validate_warehouses() + + def validate_asset(self): + status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) + if status in ("Draft", "Scrapped", "Sold"): + frappe.throw(_("{0} asset cannot be transferred").format(status)) + + if company != self.company: + frappe.throw(_("Asset {0} does not belong to company {1}").format(self.asset, self.company)) + + def validate_warehouses(self): + if not self.source_warehouse: + self.source_warehouse = frappe.db.get_value("Asset", self.asset, "warehouse") + + if self.source_warehouse == self.target_warehouse: + frappe.throw(_("Source and Target Warehouse cannot be same")) + + def on_submit(self): + self.set_latest_warehouse_in_asset() + + def on_cancel(self): + self.set_latest_warehouse_in_asset() + + def set_latest_warehouse_in_asset(self): + latest_movement_entry = frappe.db.sql("""select target_warehouse from `tabAsset Movement` + where asset=%s and docstatus=1 and company=%s + order by transaction_date desc limit 1""", (self.asset, self.company)) + + if latest_movement_entry: + warehouse = latest_movement_entry[0][0] + else: + warehouse = frappe.db.sql("""select source_warehouse from `tabAsset Movement` + where asset=%s and docstatus=2 and company=%s + order by transaction_date asc limit 1""", (self.asset, self.company))[0][0] + + frappe.db.set_value("Asset", self.asset, "warehouse", warehouse) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.js b/erpnext/assets/doctype/asset_movement/test_asset_movement.js new file mode 100644 index 0000000000..b9515763c4 --- /dev/null +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Movement", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Movement + () => frappe.tests.make('Asset Movement', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py new file mode 100644 index 0000000000..bf3e37b825 --- /dev/null +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +from frappe.utils import now +import unittest +from erpnext.assets.doctype.asset.test_asset import create_asset + + +class TestAssetMovement(unittest.TestCase): + def test_movement(self): + asset = create_asset() + + if asset.docstatus == 0: + asset.submit() + + movement1 = create_asset_movement(asset, target_warehouse="_Test Warehouse 1 - _TC") + self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse 1 - _TC") + + movement2 = create_asset_movement(asset, target_warehouse="_Test Warehouse 2 - _TC") + self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse 2 - _TC") + + movement1.cancel() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse 2 - _TC") + + movement2.cancel() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "warehouse"), "_Test Warehouse - _TC") + + asset.load_from_db() + asset.cancel() + frappe.delete_doc("Asset", asset.name) + + +def create_asset_movement(asset, target_warehouse, transaction_date=None): + if not transaction_date: + transaction_date = now() + + movement = frappe.new_doc("Asset Movement") + movement.update({ + "asset": asset.name, + "transaction_date": transaction_date, + "target_warehouse": target_warehouse, + "company": asset.company + }) + + movement.insert() + movement.submit() + + return movement diff --git a/erpnext/assets/doctype/asset_repair/__init__.py b/erpnext/assets/doctype/asset_repair/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js new file mode 100644 index 0000000000..4ba2b4474a --- /dev/null +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -0,0 +1,21 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Repair', { + repair_status: (frm) => { + if (frm.doc.completion_date && frm.doc.repair_status == "Completed") { + frappe.call ({ + method: "erpnext.assets.doctype.asset_repair.asset_repair.get_downtime", + args: { + "failure_date":frm.doc.failure_date, + "completion_date":frm.doc.completion_date + }, + callback: function(r) { + if(r.message) { + frm.set_value("downtime", r.message + " Hrs"); + } + } + }); + } + } +}); diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json new file mode 100644 index 0000000000..ccba908bbc --- /dev/null +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -0,0 +1,718 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2017-10-23 11:38:54.004355", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_name", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Asset Name", + "length": 0, + "no_copy": 0, + "options": "Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 0, + "options": "ARLOG-", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_code", + "fieldtype": "Read Only", + "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": "Item Code", + "length": 0, + "no_copy": 0, + "options": "asset_name.item_code", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_name", + "fieldtype": "Read Only", + "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": "Item Name", + "length": 0, + "no_copy": 0, + "options": "asset_name.item_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "failure_date", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Failure Date", + "length": 0, + "no_copy": 0, + "options": "", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assign_to", + "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": "Assign To", + "length": 0, + "no_copy": 0, + "options": "User", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assign_to_name", + "fieldtype": "Read Only", + "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": "Assign To Name", + "length": 0, + "no_copy": 0, + "options": "assign_to.full_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_6", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "completion_date", + "fieldtype": "Datetime", + "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": "Completion Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Pending", + "fieldname": "repair_status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Repair Status", + "length": 0, + "no_copy": 1, + "options": "Pending\nCompleted\nCancelled", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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, + "width": "" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Long Text", + "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": "Error Description", + "length": 0, + "no_copy": 0, + "options": "", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_9", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "actions_performed", + "fieldtype": "Long Text", + "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": "Actions performed", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_17", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "downtime", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Downtime", + "length": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_19", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repair_cost", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repair Cost", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "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", + "length": 0, + "no_copy": 1, + "options": "Asset Repair", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-24 16:55:03.424503", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Repair", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py new file mode 100644 index 0000000000..049b931b5e --- /dev/null +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import time_diff_in_hours +from frappe.model.document import Document + +class AssetRepair(Document): + def validate(self): + if self.repair_status == "Completed" and not self.completion_date: + frappe.throw(_("Please select Completion Date for Completed Repair")) + + +@frappe.whitelist() +def get_downtime(failure_date, completion_date): + downtime = time_diff_in_hours(completion_date, failure_date) + return round(downtime, 2) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.js b/erpnext/assets/doctype/asset_repair/test_asset_repair.js new file mode 100644 index 0000000000..7424ffe2b8 --- /dev/null +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Repair", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Repair + () => frappe.tests.make('Asset Repair', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py new file mode 100644 index 0000000000..3d325a9683 --- /dev/null +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestAssetRepair(unittest.TestCase): + pass diff --git a/erpnext/assets/doctype/depreciation_schedule/__init__.py b/erpnext/assets/doctype/depreciation_schedule/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json new file mode 100644 index 0000000000..330347240d --- /dev/null +++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json @@ -0,0 +1,226 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "", + "beta": 0, + "creation": "2016-03-02 15:11:01.278862", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "schedule_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Schedule Date", + "length": 0, + "no_copy": 1, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depreciation_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Depreciation Amount", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "accumulated_depreciation_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Accumulated Depreciation Amount", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.docstatus==1", + "fieldname": "journal_entry", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Journal Entry", + "length": 0, + "no_copy": 1, + "options": "Journal Entry", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())", + "fieldname": "make_depreciation_entry", + "fieldtype": "Button", + "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": "Make Depreciation Entry", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-10-19 16:30:13.738170", + "modified_by": "Administrator", + "module": "Assets", + "name": "Depreciation Schedule", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.py b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.py new file mode 100644 index 0000000000..54fba3f68c --- /dev/null +++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class DepreciationSchedule(Document): + pass diff --git a/erpnext/assets/doctype/maintenance_team_member/__init__.py b/erpnext/assets/doctype/maintenance_team_member/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.js b/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.js new file mode 100644 index 0000000000..2db712546c --- /dev/null +++ b/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Maintenance Team Member', { + refresh: function() { + + } +}); diff --git a/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.json b/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.json new file mode 100644 index 0000000000..195661a802 --- /dev/null +++ b/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.json @@ -0,0 +1,136 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:team_member", + "beta": 0, + "creation": "2016-10-26 10:56:04.534717", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "team_member", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Team Member", + "length": 0, + "no_copy": 0, + "options": "User", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "full_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Full Name", + "length": 0, + "no_copy": 0, + "options": "team_member.full_name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maintenance_role", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Maintenance Role", + "length": 0, + "no_copy": 0, + "options": "Role", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-10-24 15:51:58.295745", + "modified_by": "Administrator", + "module": "Assets", + "name": "Maintenance Team Member", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.py b/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.py new file mode 100644 index 0000000000..3d9e555db6 --- /dev/null +++ b/erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class MaintenanceTeamMember(Document): + pass diff --git a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js new file mode 100644 index 0000000000..d942e2a156 --- /dev/null +++ b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Maintenance Team Member", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Maintenance Team Member + () => frappe.tests.make('Maintenance Team Member', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py new file mode 100644 index 0000000000..c805e56e5c --- /dev/null +++ b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestMaintenanceTeamMember(unittest.TestCase): + pass diff --git a/erpnext/assets/report/__init__.py b/erpnext/assets/report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/report/asset_maintenance/__init__.py b/erpnext/assets/report/asset_maintenance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/report/asset_maintenance/asset_maintenance.json b/erpnext/assets/report/asset_maintenance/asset_maintenance.json new file mode 100644 index 0000000000..100c8d86ae --- /dev/null +++ b/erpnext/assets/report/asset_maintenance/asset_maintenance.json @@ -0,0 +1,24 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2017-10-24 13:12:39.946874", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "json": "{\"add_total_row\": 0, \"sort_by\": \"Asset Maintenance.modified\", \"add_totals_row\": 0, \"sort_order\": \"desc\", \"sort_by_next\": null, \"filters\": [], \"sort_order_next\": \"desc\", \"columns\": [[\"name\", \"Asset Maintenance\"], [\"asset_name\", \"Asset Maintenance\"], [\"item_code\", \"Asset Maintenance\"], [\"maintenance_type\", \"Asset Maintenance Task\"], [\"maintenance_task\", \"Asset Maintenance Task\"], [\"description\", \"Asset Maintenance Task\"], [\"assign_to_name\", \"Asset Maintenance Task\"], [\"maintenance_status\", \"Asset Maintenance Task\"], [\"next_due_date\", \"Asset Maintenance Task\"], [\"last_completion_date\", \"Asset Maintenance Task\"]]}", + "modified": "2017-10-27 22:49:56.286781", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance", + "owner": "Administrator", + "ref_doctype": "Asset Maintenance", + "report_name": "Asset Maintenance", + "report_type": "Report Builder", + "roles": [ + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 6f4dd03883..cf35ef940a 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -98,14 +98,6 @@ def get_data(): { "type": "doctype", "name": "Item", - }, - { - "type": "doctype", - "name": "Asset", - }, - { - "type": "doctype", - "name": "Asset Category", } ] }, @@ -274,11 +266,6 @@ def get_data(): "name": "Period Closing Voucher", "description": _("Close Balance Sheet and book Profit or Loss.") }, - { - "type": "doctype", - "name": "Asset Movement", - "description": _("Transfer an asset from one warehouse to another") - }, { "type": "doctype", "name": "Cheque Print Template", diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py new file mode 100644 index 0000000000..1001e948ec --- /dev/null +++ b/erpnext/config/assets.py @@ -0,0 +1,77 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return [ + { + "label": _("Assets"), + "items": [ + { + "type": "doctype", + "name": "Asset", + }, + { + "type": "doctype", + "name": "Asset Category", + } + ] + }, + { + "label": _("Maintenance"), + "items": [ + { + "type": "doctype", + "name": "Asset Maintenance Team", + }, + { + "type": "doctype", + "name": "Asset Maintenance", + }, + { + "type": "doctype", + "name": "Asset Maintenance Tasks", + }, + { + "type": "doctype", + "name": "Asset Maintenance Log", + }, + { + "type": "doctype", + "name": "Asset Repair", + }, + ] + }, + { + "label": _("Tools"), + "items": [ + { + "type": "doctype", + "name": "Asset Movement", + "description": _("Transfer an asset from one warehouse to another") + }, + ] + }, + { + "label": _("Reports"), + "icon": "fa fa-table", + "items": [ + { + "type": "report", + "name": "Asset Depreciation Ledger", + "doctype": "Asset", + "is_query_report": True, + }, + { + "type": "report", + "name": "Asset Depreciations and Balances", + "doctype": "Asset", + "is_query_report": True, + }, + { + "type": "report", + "name": "Asset Maintenance", + "doctype": "Asset Maintenance" + }, + ] + } + ] \ No newline at end of file diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py index ce6c0c3f43..d204ed81d2 100644 --- a/erpnext/config/desktop.py +++ b/erpnext/config/desktop.py @@ -293,5 +293,11 @@ def get_data(): "_doctype": "Restaurant", "link": "List/Restaurant", "label": _("Restaurant") - } + }, + { + "module_name": "Assets", + "color": "#4286f4", + "icon": "octicon octicon-database", + "type": "module" + }, ] diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py index b2db39c9f0..e6d1687202 100644 --- a/erpnext/demo/user/fixed_asset.py +++ b/erpnext/demo/user/fixed_asset.py @@ -6,8 +6,8 @@ from __future__ import unicode_literals import frappe from frappe.utils.make_random import get_random -from erpnext.accounts.doctype.asset.asset import make_purchase_invoice, make_sales_invoice -from erpnext.accounts.doctype.asset.depreciation import post_depreciation_entries, scrap_asset +from erpnext.assets.doctype.asset.asset import make_purchase_invoice, make_sales_invoice +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset def work(): frappe.set_user(frappe.db.get_global('demo_accounts_user')) diff --git a/erpnext/docs/assets/img/accounts/asset-category.png b/erpnext/docs/assets/img/asset/asset-category.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-category.png rename to erpnext/docs/assets/img/asset/asset-category.png diff --git a/erpnext/docs/assets/img/accounts/asset-graph.png b/erpnext/docs/assets/img/asset/asset-graph.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-graph.png rename to erpnext/docs/assets/img/asset/asset-graph.png diff --git a/erpnext/docs/assets/img/accounts/asset-item.png b/erpnext/docs/assets/img/asset/asset-item.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-item.png rename to erpnext/docs/assets/img/asset/asset-item.png diff --git a/erpnext/docs/assets/img/accounts/asset-movement-using-button.png b/erpnext/docs/assets/img/asset/asset-movement-using-button.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-movement-using-button.png rename to erpnext/docs/assets/img/asset/asset-movement-using-button.png diff --git a/erpnext/docs/assets/img/accounts/asset-movement.png b/erpnext/docs/assets/img/asset/asset-movement.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-movement.png rename to erpnext/docs/assets/img/asset/asset-movement.png diff --git a/erpnext/docs/assets/img/accounts/asset-purchase-invoice.png b/erpnext/docs/assets/img/asset/asset-purchase-invoice.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-purchase-invoice.png rename to erpnext/docs/assets/img/asset/asset-purchase-invoice.png diff --git a/erpnext/docs/assets/img/accounts/asset-sales.png b/erpnext/docs/assets/img/asset/asset-sales.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset-sales.png rename to erpnext/docs/assets/img/asset/asset-sales.png diff --git a/erpnext/docs/assets/img/accounts/asset.png b/erpnext/docs/assets/img/asset/asset.png similarity index 100% rename from erpnext/docs/assets/img/accounts/asset.png rename to erpnext/docs/assets/img/asset/asset.png diff --git a/erpnext/docs/assets/img/asset/asset_maintenance.png b/erpnext/docs/assets/img/asset/asset_maintenance.png new file mode 100644 index 0000000000000000000000000000000000000000..725cf62809a16c56be1bb36d104b27504128c76b GIT binary patch literal 275751 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

>KhJd>nuxK%=!dUa{sp&@prl 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