From bf7e012bde846658584b3091776cead5cab5ec4e Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Tue, 10 Sep 2019 15:43:45 +0530 Subject: [PATCH 001/131] feat: add statuses for Serial No --- erpnext/stock/doctype/serial_no/serial_no_list.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 erpnext/stock/doctype/serial_no/serial_no_list.js diff --git a/erpnext/stock/doctype/serial_no/serial_no_list.js b/erpnext/stock/doctype/serial_no/serial_no_list.js new file mode 100644 index 0000000000..559a9a7b8a --- /dev/null +++ b/erpnext/stock/doctype/serial_no/serial_no_list.js @@ -0,0 +1,14 @@ +frappe.listview_settings['Serial No'] = { + add_fields: ["is_cancelled", "item_code", "warehouse", "warranty_expiry_date", "delivery_document_type"], + get_indicator: (doc) => { + if (doc.is_cancelled) { + return [__("Cancelled"), "red", "is_cancelled,=,Yes"]; + } else if (doc.delivery_document_type) { + return [__("Delivered"), "green", "delivery_document_type,is,set|is_cancelled,=,No"]; + } else if (doc.warranty_expiry_date && frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0) { + return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set|is_cancelled,=,No"] + } else { + return [__("Active"), "green", "delivery_document_type,is,not set|is_cancelled,=,No"]; + }; + } +}; From a656151ee970fae142c03bcce04dd5b3dde7578e Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 12 Sep 2019 16:00:25 +0530 Subject: [PATCH 002/131] fix: operating cost calculation in JS --- erpnext/manufacturing/doctype/work_order/work_order.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index ce7b4f9425..d82158af33 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -385,6 +385,11 @@ frappe.ui.form.on("Work Order", { } }); } + }, + + additional_operating_cost: function(frm) { + erpnext.work_order.calculate_cost(frm.doc); + erpnext.work_order.calculate_total_cost(frm); } }); @@ -524,9 +529,8 @@ erpnext.work_order = { }, calculate_total_cost: function(frm) { - var variable_cost = frm.doc.actual_operating_cost ? - flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost); - frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)); + let variable_cost = flt(frm.doc.actual_operating_cost) || flt(frm.doc.planned_operating_cost); + frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) }, set_default_warehouse: function(frm) { From adc15295678a28d0ae9275b4cf9020003774cbf8 Mon Sep 17 00:00:00 2001 From: Vijaya Raghavan Date: Sun, 13 Oct 2019 21:41:02 +0530 Subject: [PATCH 003/131] fixed typo fixes type in filter parameter --- erpnext/hr/doctype/leave_application/leave_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 737f602883..5e71c00755 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -125,7 +125,7 @@ class LeaveApplication(Document): status = "Half Day" if date == self.half_day_date else "On Leave" attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee, - attenance_date = date, docstatus = ('!=', 2))) + attendance_date = date, docstatus = ('!=', 2))) if attendance_name: # update existing attendance, change absent to on leave From f935e3792528a05485f7bcefb0f0b231140db698 Mon Sep 17 00:00:00 2001 From: Sagar Gharge Date: Tue, 15 Oct 2019 14:38:04 +0530 Subject: [PATCH 004/131] [Fix] restricted duplicate guardians on student doctype (#19194) --- erpnext/education/doctype/student/student.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/education/doctype/student/student.js b/erpnext/education/doctype/student/student.js index 2c933e28b7..b6e741c4da 100644 --- a/erpnext/education/doctype/student/student.js +++ b/erpnext/education/doctype/student/student.js @@ -27,3 +27,16 @@ frappe.ui.form.on('Student', { } } }); + +frappe.ui.form.on('Student Guardian', { + guardians_add: function(frm){ + frm.fields_dict['guardians'].grid.get_field('guardian').get_query = function(doc){ + var guardian_list = []; + if(!doc.__islocal) guardian_list.push(doc.guardian); + $.each(doc.guardians, function(idx, val){ + if (val.guardian) guardian_list.push(val.guardian); + }); + return { filters: [['Guardian', 'name', 'not in', guardian_list]] }; + }; + } +}); From 441720df758ebd6f5420c067686f68c0d0fc7248 Mon Sep 17 00:00:00 2001 From: joelios Date: Tue, 15 Oct 2019 15:42:51 +0200 Subject: [PATCH 005/131] fix: linking of time_log to timesheet from project (frappe/erpnext #19315 ) --- erpnext/projects/doctype/project/project.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index beba2bbe74..192e1bc2a5 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -11,6 +11,9 @@ frappe.ui.form.on("Project", { // add a new row and set the project let time_log = frappe.model.get_new_doc('Timesheet Detail'); time_log.project = frm.doc.name; + time_log.parent = new_doc.name; + time_log.parentfield = 'time_logs'; + time_log.parenttype = 'Timesheet'; new_doc.time_logs = [time_log]; frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); From 8aa0a280b201be9bbd043db3c1f3288853e64da5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 15 Oct 2019 19:17:20 +0530 Subject: [PATCH 006/131] Revert "fix: Allow disabled links in issue doctype (#19265)" This reverts commit 29a5756f87ccb07bb52ffc9fbc4db0bee891c295. --- erpnext/support/doctype/issue/issue.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index cdf5fedcb8..b748e3fa46 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -22,7 +22,6 @@ class Issue(Document): return "{0}: {1}".format(_(self.status), self.subject) def validate(self): - self.flags.ignore_disabled = 1 if self.is_new() and self.via_customer_portal: self.flags.create_communication = True From 83740de636a456f7e44c8143f917f3be41543893 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 5 Jul 2019 12:09:22 +0530 Subject: [PATCH 007/131] fix(selling): Add missing label to company address field --- .../doctype/sales_order/sales_order.json | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 78e7b4abb8..b5bb3091f8 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -31,8 +31,8 @@ "contact_phone", "contact_mobile", "contact_email", - "company_address_display", "company_address", + "company_address_display", "col_break46", "shipping_address_name", "shipping_address", @@ -342,12 +342,13 @@ { "fieldname": "company_address_display", "fieldtype": "Small Text", + "label": "Company Address", "read_only": 1 }, { "fieldname": "company_address", "fieldtype": "Link", - "label": "Company Address", + "label": "Company Address Name", "options": "Address" }, { @@ -682,10 +683,10 @@ "label": "Additional Discount and Coupon Code" }, { - "fieldname": "coupon_code", - "fieldtype": "Link", - "label": "Coupon Code", - "options": "Coupon Code" + "fieldname": "coupon_code", + "fieldtype": "Link", + "label": "Coupon Code", + "options": "Coupon Code" }, { "default": "Grand Total", @@ -1192,7 +1193,7 @@ "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, - "modified": "2019-10-14 08:46:07.540565", + "modified": "2019-10-16 00:45:55.686974", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", @@ -1269,4 +1270,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file From d583e412c9fab0c12993852073126b07c39d8ac4 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Tue, 22 Oct 2019 12:33:09 +0530 Subject: [PATCH 008/131] Add generic 'Appointment' doctype --- .../doctype/appointment/__init__.py | 0 .../doctype/appointment/appointment.js | 8 ++ .../doctype/appointment/appointment.json | 103 ++++++++++++++++++ .../doctype/appointment/appointment.py | 10 ++ .../doctype/appointment/test_appointment.py | 10 ++ 5 files changed, 131 insertions(+) create mode 100644 erpnext/communication/doctype/appointment/__init__.py create mode 100644 erpnext/communication/doctype/appointment/appointment.js create mode 100644 erpnext/communication/doctype/appointment/appointment.json create mode 100644 erpnext/communication/doctype/appointment/appointment.py create mode 100644 erpnext/communication/doctype/appointment/test_appointment.py diff --git a/erpnext/communication/doctype/appointment/__init__.py b/erpnext/communication/doctype/appointment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/communication/doctype/appointment/appointment.js b/erpnext/communication/doctype/appointment/appointment.js new file mode 100644 index 0000000000..4e41047fa1 --- /dev/null +++ b/erpnext/communication/doctype/appointment/appointment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Appointment', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/communication/doctype/appointment/appointment.json b/erpnext/communication/doctype/appointment/appointment.json new file mode 100644 index 0000000000..7d808486ab --- /dev/null +++ b/erpnext/communication/doctype/appointment/appointment.json @@ -0,0 +1,103 @@ +{ + "autoname": "APMT.#####", + "creation": "2019-10-22 12:06:17.491113", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "start_time", + "appointment_duration", + "timezone", + "atendees_section", + "requester_type", + "requester", + "col_br_1", + "invitee_type", + "invitee" + ], + "fields": [ + { + "fieldname": "start_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Start Time", + "reqd": 1 + }, + { + "fieldname": "appointment_duration", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Duration in minutes", + "reqd": 1 + }, + { + "fieldname": "requester", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Appointment Requester ", + "options": "requester_type", + "reqd": 1 + }, + { + "fieldname": "invitee", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Appointment Invitee", + "options": "invitee_type", + "reqd": 1 + }, + { + "fieldname": "timezone", + "fieldtype": "Data", + "label": "Requester's Timezone", + "reqd": 1 + }, + { + "fieldname": "atendees_section", + "fieldtype": "Section Break", + "label": "Atendees" + }, + { + "fieldname": "requester_type", + "fieldtype": "Link", + "label": "Requester Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "col_br_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "invitee_type", + "fieldtype": "Link", + "label": "Invitee Type", + "options": "DocType", + "reqd": 1 + } + ], + "modified": "2019-10-22 12:18:20.719128", + "modified_by": "Administrator", + "module": "Communication", + "name": "Appointment", + "name_case": "UPPER CASE", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/communication/doctype/appointment/appointment.py b/erpnext/communication/doctype/appointment/appointment.py new file mode 100644 index 0000000000..204b066031 --- /dev/null +++ b/erpnext/communication/doctype/appointment/appointment.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, 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 Appointment(Document): + pass diff --git a/erpnext/communication/doctype/appointment/test_appointment.py b/erpnext/communication/doctype/appointment/test_appointment.py new file mode 100644 index 0000000000..702ac7176f --- /dev/null +++ b/erpnext/communication/doctype/appointment/test_appointment.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestAppointment(unittest.TestCase): + pass From 233c0bc779effa24f5ec07d19b211fd09defd03f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 24 Oct 2019 15:45:32 +0530 Subject: [PATCH 009/131] fix: sync delivery note status in both list view and form view --- erpnext/stock/doctype/delivery_note/delivery_note_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index 6fc51ecdd9..0ae7c37b3f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -6,9 +6,9 @@ frappe.listview_settings['Delivery Note'] = { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; - } else if (doc.grand_total !== 0 && flt(doc.per_billed, 2) < 100) { + } else if (flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; - } else if (doc.grand_total === 0 || flt(doc.per_billed, 2) == 100) { + } else if (flt(doc.per_billed, 2) == 100) { return [__("Completed"), "green", "per_billed,=,100"]; } }, From 9f483c1fa93baba04ae2735c58feab88ffbab1b6 Mon Sep 17 00:00:00 2001 From: Jai Chavan <40264279+jaichavan@users.noreply.github.com> Date: Sat, 26 Oct 2019 18:45:01 +0530 Subject: [PATCH 010/131] fix: Spelling of Variance --- .../report/budget_variance_report/budget_variance_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 146c10c222..8d65ac8714 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -69,7 +69,7 @@ def get_columns(filters): for year in fiscal_year: for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): if filters["period"] == "Yearly": - labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])] + labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Variance ") + " " + str(year[0])] for label in labels: columns.append(label+":Float:150") else: From 07ae3abf34f504e9766b7a34f9bc974ac4d13843 Mon Sep 17 00:00:00 2001 From: EconCode <56735547+EconCode@users.noreply.github.com> Date: Sat, 26 Oct 2019 16:31:29 +0200 Subject: [PATCH 011/131] fix: unregistering url wrong formated string Fixed the formated string for unregistering webhooks. String should look like: "/admin/api/2019-04/webhooks/#{webhook_id}.json" Taken from shopify api documentation - deleting webhooks: https://help.shopify.com/en/api/reference/events/webhook?api[version]=2019-04#destroy-2019-04 --- .../doctype/shopify_settings/shopify_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index a4332b199e..46422f54ed 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -50,7 +50,7 @@ class ShopifySettings(Document): deleted_webhooks = [] for d in self.webhooks: - url = get_shopify_url('admin/api/2019-04/webhooks.json'.format(d.webhook_id), self) + url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self) try: res = session.delete(url, headers=get_header(self)) res.raise_for_status() From 83b58352cec927b8b8baec9ebf23dd45a3749195 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 28 Oct 2019 12:05:30 +0530 Subject: [PATCH 012/131] fix: Overwrite default cost center if item has default cost center set. (#19405) --- erpnext/selling/doctype/sales_order/sales_order.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index af78ab250d..c4c3c0f81e 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -205,7 +205,7 @@ class SalesOrder(SellingController): if self.coupon_code: from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count update_coupon_code_count(self.coupon_code,'cancelled') - + def update_project(self): if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') != "Each Transaction": return @@ -661,12 +661,15 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): if source_parent.project: target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") - if not target.cost_center and target.item_code: + if target.item_code: item = get_item_defaults(target.item_code, source_parent.company) item_group = get_item_group_defaults(target.item_code, source_parent.company) - target.cost_center = item.get("selling_cost_center") \ + cost_center = item.get("selling_cost_center") \ or item_group.get("selling_cost_center") + if cost_center: + target.cost_center = cost_center + doclist = get_mapped_doc("Sales Order", source_name, { "Sales Order": { "doctype": "Sales Invoice", From 9942fcc8cb8d2d45ebd285b4876e2f69c725f897 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 28 Oct 2019 12:08:31 +0530 Subject: [PATCH 013/131] fix: Better validation msg for difference account in Stock Entry (#19400) * fix: Better validation msg for difference account * fix: Make primary info bold --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7fa7d3b0b6..55e02a46ff 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -241,7 +241,9 @@ class StockEntry(StockController): for d in self.get("items"): if not d.expense_account: - frappe.throw(_("Please enter Difference Account")) + frappe.throw(_("Please enter Difference Account or set default Stock Adjustment Account for company {0}") + .format(frappe.bold(self.company))) + elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss": frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError) From 8916916a03cffd22b9c26b8a0df7843d37c9ad01 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 28 Oct 2019 13:00:18 +0530 Subject: [PATCH 014/131] fix: 'NoneType' object is not iterable --- .../account_balance_timeline/account_balance_timeline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index bc07b6d807..d098d8421c 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe, json +from frappe import _ from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate from erpnext.accounts.report.general_ledger.general_ledger import execute from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan @@ -24,6 +25,9 @@ def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_d account = filters.get("account") company = filters.get("company") + if not account and chart: + frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart)) + if not to_date: to_date = nowdate() if not from_date: From 3dbef9de74636f034629273f2875c498c4d027a0 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 28 Oct 2019 15:48:10 +0530 Subject: [PATCH 015/131] fix: Search field entries included in Item Link field query --- erpnext/controllers/queries.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 2f6b59f0fb..ac89588978 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -152,6 +152,17 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters): def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] + #Get searchfields from meta and use in Item Link field query + meta = frappe.get_meta("Item") + searchfields = meta.get_search_fields() + + fields = [f for f in searchfields if not f in ["name", "item_group", "description"]] + fields = ", ".join(fields) + + searchfields = searchfields + [f for f in [searchfield or "name", "item_code", "item_group", "item_name"] + if not f in searchfields] + searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 @@ -162,17 +173,14 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, tabItem.item_group, if(length(tabItem.description) > 40, \ - concat(substr(tabItem.description, 1, 40), "..."), description) as decription + concat(substr(tabItem.description, 1, 40), "..."), description) as description, + {fields} from tabItem where tabItem.docstatus < 2 and tabItem.has_variants=0 and tabItem.disabled=0 and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00') - and (tabItem.`{key}` LIKE %(txt)s - or tabItem.item_code LIKE %(txt)s - or tabItem.item_group LIKE %(txt)s - or tabItem.item_name LIKE %(txt)s - or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) + and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) {description_cond}) {fcond} {mcond} order by @@ -182,6 +190,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals name, item_name limit %(start)s, %(page_len)s """.format( key=searchfield, + fields=fields, + scond=searchfields, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'), description_cond = description_cond), From 07b908c0c560f6fa59710ff798ad90fa3077a838 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 28 Oct 2019 16:00:24 +0530 Subject: [PATCH 016/131] patch: Missing Patch for item Tax template --- .../accounts/doctype/accounts_settings/accounts_settings.py | 4 ++-- erpnext/patches.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 3222aeb085..2473d715d0 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -15,8 +15,8 @@ class AccountsSettings(Document): frappe.clear_cache() def validate(self): - for f in ["add_taxes_from_item_tax_template"]: - frappe.db.set_default(f, self.get(f, "")) + frappe.db.set_default("add_taxes_from_item_tax_template", + self.get("add_taxes_from_item_tax_template", 0)) self.validate_stale_days() self.enable_payment_schedule_in_print() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ee6bdff661..94736e08e3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -641,3 +641,4 @@ erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings +execute:frappe.db.set_value("Accounts Settings", None, "add_taxes_from_item_tax_template", 1) #setting default tax template enable \ No newline at end of file From 88d2c973913b68c0256e7fb4dcab35428585083b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 28 Oct 2019 17:37:55 +0530 Subject: [PATCH 017/131] fix: default accounts are not added on creation of company --- erpnext/setup/doctype/company/company.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index e69a6aaded..8b42b19921 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -33,6 +33,10 @@ class Company(NestedSet): return exists def validate(self): + self.update_default_account = False + if self.is_new(): + self.update_default_account = True + self.validate_abbr() self.validate_default_accounts() self.validate_currency() @@ -203,8 +207,8 @@ class Company(NestedSet): "default_expense_account": "Cost of Goods Sold" }) - for default_account in default_accounts: - if self.is_new() or frappe.flags.in_test or frappe.flags.in_demo: + if self.update_default_account or frappe.flags.in_test: + for default_account in default_accounts: self._set_default_account(default_account, default_accounts.get(default_account)) if not self.default_income_account: From 4f7885f1aa63f99320c26c8b87b2d32bfacc35ec Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 29 Oct 2019 13:20:17 +0530 Subject: [PATCH 018/131] chore: fix unexpected keyword in shopify_settings Signed-off-by: Chinmay D. Pai --- .../doctype/shopify_settings/shopify_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index a4332b199e..0cad0cca72 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -43,7 +43,7 @@ class ShopifySettings(Document): d.raise_for_status() self.update_webhook_table(method, d.json()) except Exception as e: - make_shopify_log(status="Warning", message=e, exception=False) + make_shopify_log(status="Warning", exception=e, rollback=True) def unregister_webhooks(self): session = get_request_session() From 702f9f929b46b3edd25ec53b3b3cb9186beeb10d Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 29 Oct 2019 13:30:13 +0530 Subject: [PATCH 019/131] chore: rename undefined variable in shopify_log Signed-off-by: Chinmay D. Pai --- erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py index 7d3f572978..a2b6af99b2 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py +++ b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py @@ -39,7 +39,7 @@ def get_message(exception): if hasattr(exception, 'message'): message = exception.message elif hasattr(exception, '__str__'): - message = e.__str__() + message = exception.__str__() else: message = "Something went wrong while syncing" return message From b051fa37df682efc9ab786076c6b8f08a3283844 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 29 Oct 2019 14:56:03 +0530 Subject: [PATCH 020/131] fix: patch, item tax template showing 'Untitled' in the name --- .../move_item_tax_to_item_tax_template.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 412f32030a..6769144aeb 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -1,5 +1,6 @@ import frappe import json +from frappe.model.naming import make_autoname from six import iteritems def execute(): @@ -7,7 +8,6 @@ def execute(): return old_item_taxes = {} item_tax_templates = {} - rename_template_to_untitled = [] for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1): old_item_taxes.setdefault(d.item_code, []) @@ -34,7 +34,7 @@ def execute(): for d in old_item_taxes[item_code]: item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, rename_template_to_untitled, + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) # update the item tax table @@ -53,26 +53,19 @@ def execute(): for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` where ifnull(item_tax_rate, '') not in ('', '{{}}')""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) - item_tax_template = get_item_tax_template(item_tax_templates, rename_template_to_untitled, + item_tax_template = get_item_tax_template(item_tax_templates, item_tax_map, d.item_code, d.parent) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template) - idx = 1 - for oldname in rename_template_to_untitled: - frappe.rename_doc("Item Tax Template", oldname, "Untitled {}".format(idx)) - idx += 1 - settings = frappe.get_single("Accounts Settings") settings.add_taxes_from_item_tax_template = 0 settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, item_code, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: - if not parent: - rename_template_to_untitled.append(template) return template # if no item tax template found, create one @@ -97,5 +90,11 @@ def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_ item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_templates[item_tax_template.title][tax_type] = tax_rate - item_tax_template.save() + + try: + item_tax_template.save() + except frappe.DuplicateEntryError: + item_tax_template.name = make_autoname(item_tax_template.title + "/.###") + item_tax_template.save() + return item_tax_template.name From 2a72d1fee4bf8d6baed9632bf6dbf58495bc6fd1 Mon Sep 17 00:00:00 2001 From: ahmadRagheb Date: Tue, 29 Oct 2019 19:49:08 +0200 Subject: [PATCH 021/131] Update salary_slip.py getdate imported twice --- erpnext/hr/doctype/salary_slip/salary_slip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 27a51c30e7..46be4fe287 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext import datetime, math -from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, getdate +from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words from frappe.model.naming import make_autoname from frappe import msgprint, _ From abbc08cb486105d9dbedfc2a57aede39c2303f25 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 10:46:16 +0530 Subject: [PATCH 022/131] fix: hide generate schedule button on new forms --- .../doctype/maintenance_schedule/maintenance_schedule.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index ce95db3bf5..f2fc588be4 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -20,6 +20,12 @@ frappe.ui.form.on('Maintenance Schedule', { frm.set_value({transaction_date: frappe.datetime.get_today()}); } }, + refresh: function(frm) { + setTimeout(()=> { + debugger; + frm.toggle_display('generate_schedule',!(frm.is_new())); + },10); + }, customer: function(frm) { erpnext.utils.get_party_details(frm) }, From d2d0100c9a6958a7694af0575176bef497d81cd3 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 10:48:13 +0530 Subject: [PATCH 023/131] fix:better error message when sales person is none --- .../doctype/maintenance_schedule/maintenance_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 3a64e1aa67..1ad7fc8552 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -150,7 +150,7 @@ class MaintenanceSchedule(TransactionBase): elif not d.no_of_visits: throw(_("Please mention no of visits required")) elif not d.sales_person: - throw(_("Please select Incharge Person's name")) + throw(_("Please select Incharge Person's name for item: {0}".format(d.item_name))) if getdate(d.start_date) >= getdate(d.end_date): throw(_("Start date should be less than end date for Item {0}").format(d.item_code)) From 846c03889ef5a39a824b059d1924249e59b9e9ca Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 10:51:25 +0530 Subject: [PATCH 024/131] fix: hide schedule section on new forms --- .../doctype/maintenance_schedule/maintenance_schedule.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index f2fc588be4..e91f478aa4 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -24,6 +24,7 @@ frappe.ui.form.on('Maintenance Schedule', { setTimeout(()=> { debugger; frm.toggle_display('generate_schedule',!(frm.is_new())); + frm.toggle_display('schedule',!(frm.is_new())); },10); }, customer: function(frm) { From 0f0d6ce25cf2a44f18a4300d3ab0e31c2a4d6c06 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 11:08:09 +0530 Subject: [PATCH 025/131] Remove doctype related to appointment scheduling --- .../doctype/appointment/__init__.py | 0 .../doctype/appointment/appointment.js | 8 -- .../doctype/appointment/appointment.json | 103 ------------------ .../doctype/appointment/appointment.py | 10 -- .../doctype/appointment/test_appointment.py | 10 -- 5 files changed, 131 deletions(-) delete mode 100644 erpnext/communication/doctype/appointment/__init__.py delete mode 100644 erpnext/communication/doctype/appointment/appointment.js delete mode 100644 erpnext/communication/doctype/appointment/appointment.json delete mode 100644 erpnext/communication/doctype/appointment/appointment.py delete mode 100644 erpnext/communication/doctype/appointment/test_appointment.py diff --git a/erpnext/communication/doctype/appointment/__init__.py b/erpnext/communication/doctype/appointment/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/communication/doctype/appointment/appointment.js b/erpnext/communication/doctype/appointment/appointment.js deleted file mode 100644 index 4e41047fa1..0000000000 --- a/erpnext/communication/doctype/appointment/appointment.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Appointment', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/communication/doctype/appointment/appointment.json b/erpnext/communication/doctype/appointment/appointment.json deleted file mode 100644 index 7d808486ab..0000000000 --- a/erpnext/communication/doctype/appointment/appointment.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "autoname": "APMT.#####", - "creation": "2019-10-22 12:06:17.491113", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "start_time", - "appointment_duration", - "timezone", - "atendees_section", - "requester_type", - "requester", - "col_br_1", - "invitee_type", - "invitee" - ], - "fields": [ - { - "fieldname": "start_time", - "fieldtype": "Datetime", - "in_list_view": 1, - "label": "Start Time", - "reqd": 1 - }, - { - "fieldname": "appointment_duration", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Duration in minutes", - "reqd": 1 - }, - { - "fieldname": "requester", - "fieldtype": "Dynamic Link", - "in_list_view": 1, - "label": "Appointment Requester ", - "options": "requester_type", - "reqd": 1 - }, - { - "fieldname": "invitee", - "fieldtype": "Dynamic Link", - "in_list_view": 1, - "label": "Appointment Invitee", - "options": "invitee_type", - "reqd": 1 - }, - { - "fieldname": "timezone", - "fieldtype": "Data", - "label": "Requester's Timezone", - "reqd": 1 - }, - { - "fieldname": "atendees_section", - "fieldtype": "Section Break", - "label": "Atendees" - }, - { - "fieldname": "requester_type", - "fieldtype": "Link", - "label": "Requester Type", - "options": "DocType", - "reqd": 1 - }, - { - "fieldname": "col_br_1", - "fieldtype": "Column Break" - }, - { - "fieldname": "invitee_type", - "fieldtype": "Link", - "label": "Invitee Type", - "options": "DocType", - "reqd": 1 - } - ], - "modified": "2019-10-22 12:18:20.719128", - "modified_by": "Administrator", - "module": "Communication", - "name": "Appointment", - "name_case": "UPPER CASE", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/communication/doctype/appointment/appointment.py b/erpnext/communication/doctype/appointment/appointment.py deleted file mode 100644 index 204b066031..0000000000 --- a/erpnext/communication/doctype/appointment/appointment.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, 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 Appointment(Document): - pass diff --git a/erpnext/communication/doctype/appointment/test_appointment.py b/erpnext/communication/doctype/appointment/test_appointment.py deleted file mode 100644 index 702ac7176f..0000000000 --- a/erpnext/communication/doctype/appointment/test_appointment.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestAppointment(unittest.TestCase): - pass From be5c6e7aecfd82c0bb06096ba6d3d75068a9d980 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 11:14:49 +0530 Subject: [PATCH 026/131] fix:readability --- .../doctype/maintenance_schedule/maintenance_schedule.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index e91f478aa4..41c0ccad57 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -21,10 +21,9 @@ frappe.ui.form.on('Maintenance Schedule', { } }, refresh: function(frm) { - setTimeout(()=> { - debugger; - frm.toggle_display('generate_schedule',!(frm.is_new())); - frm.toggle_display('schedule',!(frm.is_new())); + setTimeout(() => { + frm.toggle_display('generate_schedule', !(frm.is_new())); + frm.toggle_display('schedule', !(frm.is_new())); },10); }, customer: function(frm) { From da5bf5a7c41925d970f43849b19da1380ae4f27a Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 11:24:58 +0530 Subject: [PATCH 027/131] fix:indentation --- .../doctype/maintenance_schedule/maintenance_schedule.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 41c0ccad57..ea48b4ef8b 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -21,10 +21,10 @@ frappe.ui.form.on('Maintenance Schedule', { } }, refresh: function(frm) { - setTimeout(() => { - frm.toggle_display('generate_schedule', !(frm.is_new())); - frm.toggle_display('schedule', !(frm.is_new())); - },10); + setTimeout(() => { + frm.toggle_display('generate_schedule', !(frm.is_new())); + frm.toggle_display('schedule', !(frm.is_new())); + },10); }, customer: function(frm) { erpnext.utils.get_party_details(frm) From e127d937f745e58b857d425d2eacc497d49e75cd Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 30 Oct 2019 13:31:05 +0530 Subject: [PATCH 028/131] fix(ExpenseClaim): List filter config for Rejected and Unpaid (#19434) --- erpnext/hr/doctype/expense_claim/expense_claim_list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_list.js b/erpnext/hr/doctype/expense_claim/expense_claim_list.js index 0e25e66687..6195ad414a 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim_list.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim_list.js @@ -2,11 +2,11 @@ frappe.listview_settings['Expense Claim'] = { add_fields: ["total_claimed_amount", "docstatus"], get_indicator: function(doc) { if(doc.status == "Paid") { - return [__("Paid"), "green", "status,=,'Paid'"]; + return [__("Paid"), "green", "status,=,Paid"]; }else if(doc.status == "Unpaid") { - return [__("Unpaid"), "orange"]; + return [__("Unpaid"), "orange", "status,=,Unpaid"]; } else if(doc.status == "Rejected") { - return [__("Rejected"), "grey"]; + return [__("Rejected"), "grey", "status,=,Rejected"]; } } }; From 07b74533fe47b671e051e512f0d83b85eb51300a Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 13:47:49 +0530 Subject: [PATCH 029/131] fix: error message matches field name --- .../doctype/maintenance_schedule/maintenance_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 1ad7fc8552..94d85f77ef 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -150,7 +150,7 @@ class MaintenanceSchedule(TransactionBase): elif not d.no_of_visits: throw(_("Please mention no of visits required")) elif not d.sales_person: - throw(_("Please select Incharge Person's name for item: {0}".format(d.item_name))) + throw(_("Please select a Sales Person for item: {0}".format(d.item_name))) if getdate(d.start_date) >= getdate(d.end_date): throw(_("Start date should be less than end date for Item {0}").format(d.item_code)) From c8333d4b41386a386d87aa8469dcf07b1cc2b768 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 30 Oct 2019 13:48:01 +0530 Subject: [PATCH 030/131] fix: codacy --- .../doctype/maintenance_schedule/maintenance_schedule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index ea48b4ef8b..e940b6050c 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -21,7 +21,7 @@ frappe.ui.form.on('Maintenance Schedule', { } }, refresh: function(frm) { - setTimeout(() => { + setTimeout(() => { frm.toggle_display('generate_schedule', !(frm.is_new())); frm.toggle_display('schedule', !(frm.is_new())); },10); From d8abac327878bf723cc2a896eaeb41d46e7614ee Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 30 Oct 2019 14:06:56 +0530 Subject: [PATCH 031/131] fix: typo in production plan --- .../manufacturing/doctype/production_plan/production_plan.js | 2 +- erpnext/translations/af.csv | 2 +- erpnext/translations/am.csv | 2 +- erpnext/translations/ar.csv | 2 +- erpnext/translations/bg.csv | 2 +- erpnext/translations/bn.csv | 2 +- erpnext/translations/bs.csv | 2 +- erpnext/translations/ca.csv | 2 +- erpnext/translations/cs.csv | 2 +- erpnext/translations/da.csv | 2 +- erpnext/translations/de.csv | 2 +- erpnext/translations/el.csv | 2 +- erpnext/translations/es.csv | 2 +- erpnext/translations/et.csv | 2 +- erpnext/translations/fa.csv | 2 +- erpnext/translations/fi.csv | 2 +- erpnext/translations/fr.csv | 2 +- erpnext/translations/gu.csv | 2 +- erpnext/translations/hi.csv | 2 +- erpnext/translations/hr.csv | 2 +- erpnext/translations/hu.csv | 2 +- erpnext/translations/id.csv | 2 +- erpnext/translations/is.csv | 2 +- erpnext/translations/it.csv | 2 +- erpnext/translations/ja.csv | 2 +- erpnext/translations/km.csv | 2 +- erpnext/translations/kn.csv | 2 +- erpnext/translations/ko.csv | 2 +- erpnext/translations/ku.csv | 2 +- erpnext/translations/lo.csv | 2 +- erpnext/translations/lt.csv | 2 +- erpnext/translations/lv.csv | 2 +- erpnext/translations/mk.csv | 2 +- erpnext/translations/ml.csv | 2 +- erpnext/translations/mr.csv | 2 +- erpnext/translations/ms.csv | 2 +- erpnext/translations/my.csv | 2 +- erpnext/translations/nl.csv | 2 +- erpnext/translations/no.csv | 2 +- erpnext/translations/pl.csv | 2 +- erpnext/translations/ps.csv | 2 +- erpnext/translations/pt.csv | 2 +- erpnext/translations/ro.csv | 2 +- erpnext/translations/ru.csv | 2 +- erpnext/translations/si.csv | 2 +- erpnext/translations/sk.csv | 2 +- erpnext/translations/sl.csv | 2 +- erpnext/translations/sq.csv | 2 +- erpnext/translations/sr.csv | 2 +- erpnext/translations/sv.csv | 2 +- erpnext/translations/sw.csv | 2 +- erpnext/translations/ta.csv | 2 +- erpnext/translations/te.csv | 2 +- erpnext/translations/th.csv | 2 +- erpnext/translations/tr.csv | 2 +- erpnext/translations/uk.csv | 2 +- erpnext/translations/ur.csv | 2 +- erpnext/translations/uz.csv | 2 +- erpnext/translations/vi.csv | 2 +- erpnext/translations/zh.csv | 2 +- erpnext/translations/zh_tw.csv | 2 +- 61 files changed, 61 insertions(+), 61 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 2aeea5827d..4b654b47e6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -103,7 +103,7 @@ frappe.ui.form.on('Production Plan', { ${__('Reserved Qty for Production: Raw materials quantity to make manufacturing items.')}
  • - ${__('Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.')} + ${__('Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.')}
  • diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index f74448e159..3cb2e8da50 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Betaaldatum apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Voorbehou Aantal: Hoeveelheid te koop bestel, maar nie afgelewer nie." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Herstel, as die gekose adres geredigeer word na die stoor" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Voorbehou Aantal vir Onderkontrakte: Hoeveelheid grondstowwe om onderverpakte items te maak. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Voorbehou Aantal vir Onderkontrakte: Hoeveelheid grondstowwe om onderverpakte items te maak. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Item Variant {0} bestaan reeds met dieselfde eienskappe DocType: Item,Hub Publishing Details,Hub Publishing Details apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Oopmaak' diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv index 6b2e47b703..1d07f516f2 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -1560,7 +1560,7 @@ DocType: Sales Invoice,Payment Due Date,ክፍያ መጠናቀቅ ያለበት apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",የተያዙ ጫፎች ብዛት ለሽያጭ የታዘዘ ፣ ግን አልደረሰም ፡፡ DocType: Drug Prescription,Interval UOM,የጊዜ ክፍተት UOM DocType: Customer,"Reselect, if the chosen address is edited after save",የተመረጠው አድራሻ ከተቀመጠ በኋላ ማስተካከያ ከተደረገበት አይምረጡ -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,የተያዙ ዕቃዎች ለንዑስ-ኮንትራክተር-ንዑስ-ንዑስ ንጥል ነገሮችን ለመስራት ጥሬ ዕቃዎች ብዛት። +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,የተያዙ ዕቃዎች ለንዑስ-ኮንትራክተር-ንዑስ-ንዑስ ንጥል ነገሮችን ለመስራት ጥሬ ዕቃዎች ብዛት። apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,ንጥል ተለዋጭ {0} ቀድሞውኑ ተመሳሳይ ባሕርያት ጋር አለ DocType: Item,Hub Publishing Details,ሃቢ የህትመት ዝርዝሮች apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','በመክፈት ላይ' diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index 05ccdbfb46..b20bf97dc2 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,تاريخ استحقاق السداد apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",الكمية المحجوزة : الكمية المطلوبة لل بيع، ولكن لم يتم تسليمها . DocType: Drug Prescription,Interval UOM,الفاصل الزمني أوم DocType: Customer,"Reselect, if the chosen address is edited after save",إعادة تحديد، إذا تم تحرير عنوان المختار بعد حفظ -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,الكمية المحجوزة للعقد من الباطن: كمية المواد الخام لصنع سلع من الباطن. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,الكمية المحجوزة للعقد من الباطن: كمية المواد الخام لصنع سلع من الباطن. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,متغير الصنف {0} موجود بالفعل مع نفس الخصائص DocType: Item,Hub Publishing Details,هاب تفاصيل النشر apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','افتتاحي' diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv index 9da1eb3a82..fbb2b0f213 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Дължимото плащане Дат apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Количество, запазено: Количество, поръчано за продажба, но не е доставено." DocType: Drug Prescription,Interval UOM,Интервал (мерна единица) DocType: Customer,"Reselect, if the chosen address is edited after save","Преименувайте отново, ако избраният адрес се редактира след запазване" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,"Количество, запазено за подизпълнение: Количество суровини за изработване на извадени продукти." +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,"Количество, запазено за подизпълнение: Количество суровини за изработване на извадени продукти." apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Позиция Variant {0} вече съществува с едни и същи атрибути DocType: Item,Hub Publishing Details,Подробна информация за издателя apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"""Начален баланс""" diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv index 67ef37725c..26a3c33890 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -1544,7 +1544,7 @@ DocType: Sales Invoice,Payment Due Date,পরিশোধযোগ্য তা apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","সংরক্ষিত পরিমাণ: পরিমাণ বিক্রয়ের জন্য অর্ডার করা হয়েছে, তবে বিতরণ করা হয়নি।" DocType: Drug Prescription,Interval UOM,অন্তর্বর্তী UOM DocType: Customer,"Reselect, if the chosen address is edited after save",সংরক্ষণ করার পরে যদি নির্বাচিত ঠিকানাটি সম্পাদনা করা হয় তবে নির্বাচন বাতিল করুন -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,সাবকন্ট্রাক্টের জন্য সংরক্ষিত পরিমাণ: উপকোট্রাক্ট আইটেমগুলি তৈরি করতে কাঁচামাল পরিমাণ। +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,সাবকন্ট্রাক্টের জন্য সংরক্ষিত পরিমাণ: উপকোট্রাক্ট আইটেমগুলি তৈরি করতে কাঁচামাল পরিমাণ। apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,আইটেম ভেরিয়েন্ট {0} ইতিমধ্যে একই বৈশিষ্ট্যাবলী সঙ্গে বিদ্যমান DocType: Item,Hub Publishing Details,হাব প্রকাশনা বিবরণ apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',' শুরু' diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv index fe7d8c828e..aa148cb135 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,Plaćanje Due Date apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Rezervirano Količina : Količina naručiti za prodaju , ali nije dostavljena ." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Ponovo odaberite, ako je izabrana adresa uređena nakon čuvanja" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Količina rezervisanog za podugovor: Količina sirovina za izradu predmeta koji se oduzimaju. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Količina rezervisanog za podugovor: Količina sirovina za izradu predmeta koji se oduzimaju. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Stavka Variant {0} već postoji s istim atributima DocType: Item,Hub Publishing Details,Detalji izdavanja stanice apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Otvaranje' diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv index da6edd5792..fd11fe6941 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,Data de pagament apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reservats Quantitat: Quantitat va ordenar a la venda, però no entregat." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Torneu a seleccionar, si l'adreça escollida s'edita després de desar-la" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Quantitat reservada per al subcontracte: quantitat de matèries primeres per fabricar articles subcontractats. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Quantitat reservada per al subcontracte: quantitat de matèries primeres per fabricar articles subcontractats. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Article Variant {0} ja existeix amb els mateixos atributs DocType: Item,Hub Publishing Details,Detalls de publicació del Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Obertura' diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv index 685966548e..43dd383f50 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -1580,7 +1580,7 @@ DocType: Sales Invoice,Payment Due Date,Splatno dne apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserved Množství: Množství objednal k prodeji, ale není doručena." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Znovu vyberte, pokud je zvolená adresa po uložení upravena" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Vyhrazeno Množství pro subdodávky: Množství surovin pro výrobu subdodávek. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Vyhrazeno Množství pro subdodávky: Množství surovin pro výrobu subdodávek. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Bod Variant {0} již existuje se stejnými vlastnostmi DocType: Item,Hub Publishing Details,Podrobnosti o publikování Hubu apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"""Otevírací""" diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv index 1f185f1acd..0120e3c661 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Sidste betalingsdato apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserveret antal: Mængde bestilt til salg, men ikke leveret." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Vælg igen, hvis den valgte adresse redigeres efter gem" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Reserveret antal til underentreprise: Råvaremængde til fremstilling af underleverede genstande. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Reserveret antal til underentreprise: Råvaremængde til fremstilling af underleverede genstande. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Item Variant {0} findes allerede med samme attributter DocType: Item,Hub Publishing Details,Hub Publishing Detaljer apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Åbner' diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 52394682f0..2e25a127d8 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,Zahlungsstichtag apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reservierte Menge: Für den Verkauf bestellte Menge, aber noch nicht geliefert." DocType: Drug Prescription,Interval UOM,Intervall UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Wählen Sie erneut, wenn die gewählte Adresse nach dem Speichern bearbeitet wird" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Reservierte Menge für Lohnbearbeiter: Rohstoffmenge für Lohnbearbeiter. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Reservierte Menge für Lohnbearbeiter: Rohstoffmenge für Lohnbearbeiter. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Artikelvariante {0} mit denselben Attributen existiert bereits DocType: Item,Hub Publishing Details,Hub-Veröffentlichungsdetails apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"""Eröffnung""" diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv index faac064b63..3e641e10a0 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,Ημερομηνία λήξης προθ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",Δεσμευμένη ποσότητα : ποσότητα που παραγγέλθηκε για πώληση αλλά δεν παραδόθηκε. DocType: Drug Prescription,Interval UOM,Διαστήματα UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Επαναφέρετε την επιλογή, εάν η επιλεγμένη διεύθυνση επεξεργαστεί μετά την αποθήκευση" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Προβλεπόμενη ποσότητα για υπεργολαβία: Ποσότητα πρώτων υλών για την πραγματοποίηση υποκλάδων. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Προβλεπόμενη ποσότητα για υπεργολαβία: Ποσότητα πρώτων υλών για την πραγματοποίηση υποκλάδων. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Θέση Παραλλαγή {0} υπάρχει ήδη με ίδια χαρακτηριστικά DocType: Item,Hub Publishing Details,Στοιχεία δημοσίευσης Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',«Άνοιγμα» diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv index 18fd8c8703..6e128923d7 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Fecha de pago apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Cantidad Reservada: Cantidad a pedir a la venta , pero no entregado." DocType: Drug Prescription,Interval UOM,Intervalo UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Vuelva a seleccionar, si la dirección elegida se edita después de guardar" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Cantidad reservada para subcontrato: Cantidad de materias primas para hacer artículos subcotractados. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Cantidad reservada para subcontrato: Cantidad de materias primas para hacer artículos subcotractados. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Artículo Variant {0} ya existe con los mismos atributos DocType: Item,Hub Publishing Details,Detalle de Publicación del Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Apertura' diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv index 7b873423c8..bd042b8093 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -1558,7 +1558,7 @@ DocType: Sales Invoice,Payment Due Date,Maksetähtpäevast apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserveeritud kogus: Müügiks tellitud kogus, kuid tarnimata." DocType: Drug Prescription,Interval UOM,Intervall UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Kui valite valitud aadressi pärast salvestamist, vali uuesti" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Allhankelepingu jaoks reserveeritud kogus: Tooraine kogus alamhangete jaoks. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Allhankelepingu jaoks reserveeritud kogus: Tooraine kogus alamhangete jaoks. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Punkt Variant {0} on juba olemas sama atribuute DocType: Item,Hub Publishing Details,Hubi avaldamise üksikasjad apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Avamine" diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv index ffd013e8dd..f9fd4279f9 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -1544,7 +1544,7 @@ DocType: Sales Invoice,Payment Due Date,پرداخت با توجه تاریخ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",Qty رزرو شده: مقدار سفارش برای فروش سفارش داده می شود ، اما تحویل داده نمی شود. DocType: Drug Prescription,Interval UOM,فاصله UOM DocType: Customer,"Reselect, if the chosen address is edited after save",در صورتی که آدرس انتخاب شده پس از ذخیره ویرایش، مجددا انتخاب کنید -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qty رزرو شده برای قراردادهای فرعی: مقدار مواد اولیه برای ساخت وسایل فرعی. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qty رزرو شده برای قراردادهای فرعی: مقدار مواد اولیه برای ساخت وسایل فرعی. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,مورد متغیر {0} در حال حاضر با ویژگی های همان وجود دارد DocType: Item,Hub Publishing Details,جزئیات انتشار هاب apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','افتتاح' diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv index c028c30ba4..1b56db2b77 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Maksun eräpäivä apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Varattu määrä: Myytävänä oleva määrä, mutta ei toimitettu." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Vahvista valinta uudelleen, jos valittua osoitetta muokataan tallennuksen jälkeen" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Varattu määrä alihankintasopimuksille: Raaka-aineiden määrä alihankittujen tuotteiden valmistamiseksi. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Varattu määrä alihankintasopimuksille: Raaka-aineiden määrä alihankittujen tuotteiden valmistamiseksi. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Tuote Variant {0} on jo olemassa samoja ominaisuuksia DocType: Item,Hub Publishing Details,Hub-julkaisutiedot apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Avattu' diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 67e9885be9..874bfb0d8b 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -1583,7 +1583,7 @@ DocType: Sales Invoice,Payment Due Date,Date d'Échéance de Paiement apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Réservés Quantité: Quantité de commande pour la vente , mais pas livré ." DocType: Drug Prescription,Interval UOM,UDM d'Intervalle DocType: Customer,"Reselect, if the chosen address is edited after save","Re-sélectionner, si l'adresse choisie est éditée après l'enregistrement" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qté réservée aux sous-traitants: quantité de matières premières permettant de fabriquer des articles sous-traités. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qté réservée aux sous-traitants: quantité de matières premières permettant de fabriquer des articles sous-traités. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,La Variante de l'Article {0} existe déjà avec les mêmes caractéristiques DocType: Item,Hub Publishing Details,Détails Publiés sur le Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Ouverture' diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv index eb35e24ebd..ffc0c4f695 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -1543,7 +1543,7 @@ DocType: Sales Invoice,Payment Due Date,ચુકવણી કારણે ત apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","રિઝર્વેટેડ ક્વોટી: વેચવા માટેનો જથ્થો આપ્યો, પરંતુ પહોંચાડ્યો નહીં." DocType: Drug Prescription,Interval UOM,અંતરાલ UOM DocType: Customer,"Reselect, if the chosen address is edited after save","રીસલેક્ટ કરો, જો સાચવેલા સરનામાંને સેવ કર્યા પછી સંપાદિત કરવામાં આવે છે" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,સબકોન્ટ્રેક્ટ માટે અનામત પ્રમાણ: સબકોટ્રેક્ટ વસ્તુઓ બનાવવા માટે કાચા માલનો જથ્થો. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,સબકોન્ટ્રેક્ટ માટે અનામત પ્રમાણ: સબકોટ્રેક્ટ વસ્તુઓ બનાવવા માટે કાચા માલનો જથ્થો. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,વસ્તુ વેરિએન્ટ {0} પહેલાથી જ લક્ષણો સાથે હાજર DocType: Item,Hub Publishing Details,હબ પબ્લિશિંગ વિગતો apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','શરૂઆત' diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv index 9b5fc417c7..d745155fd7 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -1578,7 +1578,7 @@ DocType: Sales Invoice,Payment Due Date,भुगतान की नियत apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","सुरक्षित मात्रा: मात्रा बिक्री के लिए आदेश दिया है , लेकिन नहीं पहुंचा." DocType: Drug Prescription,Interval UOM,अंतराल UOM DocType: Customer,"Reselect, if the chosen address is edited after save","अचयनित करें, अगर सहेजे जाने के बाद चुना हुआ पता संपादित किया गया है" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,सब-कॉन्ट्रैक्ट के लिए आरक्षित मात्रा: कच्चे माल की मात्रा उप-निर्मित आइटम बनाने के लिए। +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,सब-कॉन्ट्रैक्ट के लिए आरक्षित मात्रा: कच्चे माल की मात्रा उप-निर्मित आइटम बनाने के लिए। apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,मद संस्करण {0} पहले से ही एक ही गुण के साथ मौजूद है DocType: Item,Hub Publishing Details,हब प्रकाशन विवरण apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','उद्घाटन' diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv index d49b79a9a5..1e9d77ab4a 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,Plaćanje Due Date apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Rezervirano Količina : Količina naručiti za prodaju , ali nije dostavljena ." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save",Ponovno odaberite ako je odabrana adresa uređena nakon spremanja -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Količina rezerviranog za podugovor: Količina sirovina za izradu poduhvata. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Količina rezerviranog za podugovor: Količina sirovina za izradu poduhvata. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Stavka Varijanta {0} već postoji s istim atributima DocType: Item,Hub Publishing Details,Pojedinosti objavljivanja središta apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Otvaranje ' diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv index bd8a6f32ff..25ee33ba61 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Fizetési határidő apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserved Mennyiség: Rendelhető mennyiség eladó, de nem teljesített." DocType: Drug Prescription,Interval UOM,Intervallum mértékegysége DocType: Customer,"Reselect, if the chosen address is edited after save","Újra válassza ki, ha a kiválasztott cím szerkesztésre került a mentés után" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Fenntartott mennyiség az alvállalkozók számára: Nyersanyag-mennyiség az alhúzásokhoz. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Fenntartott mennyiség az alvállalkozók számára: Nyersanyag-mennyiség az alhúzásokhoz. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Tétel variáció {0} már létezik azonos Jellemzővel DocType: Item,Hub Publishing Details,Hub közzétételének részletei apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"""Nyitás""" diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv index a49187fba0..127cc572e6 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Tanggal Jatuh Tempo Pembayaran apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserved Qty: Jumlah memerintahkan untuk dijual, tapi tidak disampaikan." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Pilih ulang, jika alamat yang dipilih diedit setelah simpan" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Jumlah Pesanan untuk Sub-kontrak: Kuantitas bahan baku untuk membuat barang-barang yang disubsidi. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Jumlah Pesanan untuk Sub-kontrak: Kuantitas bahan baku untuk membuat barang-barang yang disubsidi. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Item Varian {0} sudah ada dengan atribut yang sama DocType: Item,Hub Publishing Details,Rincian Hub Publishing apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Awal' diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv index 840315752f..5e13baf524 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -1562,7 +1562,7 @@ DocType: Sales Invoice,Payment Due Date,Greiðsla Due Date apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Frátekið magn: Magn pantað til sölu, en ekki afhent." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Veldu aftur, ef valið heimilisfang er breytt eftir að vista" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Frátekið magn fyrir undirverktaka: Magn hráefna til að búa til undirverktaka hluti. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Frátekið magn fyrir undirverktaka: Magn hráefna til að búa til undirverktaka hluti. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Liður Variant {0} er þegar til staðar með sömu eiginleika DocType: Item,Hub Publishing Details,Hub Publishing Upplýsingar apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Opening' diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv index 5d3f12a796..5d38b99728 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -1581,7 +1581,7 @@ DocType: Sales Invoice,Payment Due Date,Scadenza apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Riservato Quantità : quantità ordinata in vendita , ma non consegnati ." DocType: Drug Prescription,Interval UOM,Intervallo UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Riseleziona, se l'indirizzo scelto viene modificato dopo il salvataggio" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qtà riservata per conto lavoro: quantità di materie prime per la produzione di articoli in conto lavoro. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qtà riservata per conto lavoro: quantità di materie prime per la produzione di articoli in conto lavoro. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Prodotto Modello {0} esiste già con gli stessi attributi DocType: Item,Hub Publishing Details,Dettagli di pubblicazione Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Apertura' diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv index 166d4ef724..8e72050f75 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -1589,7 +1589,7 @@ DocType: Sales Invoice,Payment Due Date,支払期日 apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",予約数量:販売用に数量が注文されていますが、納品されていません。 DocType: Drug Prescription,Interval UOM,インターバル単位 DocType: Customer,"Reselect, if the chosen address is edited after save",選択したアドレスが保存後に編集された場合は、再選択します。 -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,外注の予約数量:外注品目を作成するための原料数量。 +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,外注の予約数量:外注品目を作成するための原料数量。 apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,アイテムバリエーション{0}は既に同じ属性で存在しています DocType: Item,Hub Publishing Details,ハブ公開の詳細 apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',「オープニング」 diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv index 812f6baa78..fefd314ba3 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -1553,7 +1553,7 @@ DocType: Sales Invoice,Payment Due Date,ការទូទាត់កាលប apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",Qty បានបម្រុងទុក: បរិមាណបានបញ្ជាទិញសម្រាប់លក់ប៉ុន្តែមិនបានប្រគល់ឱ្យទេ។ DocType: Drug Prescription,Interval UOM,ចន្លោះពេលវេលា UOM DocType: Customer,"Reselect, if the chosen address is edited after save",ជ្រើសរើសបើអាសយដ្ឋានដែលបានជ្រើសត្រូវបានកែសម្រួលបន្ទាប់ពីរក្សាទុក -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qty ដែលបានបម្រុងទុកសម្រាប់កិច្ចសន្យារង: បរិមាណវត្ថុធាតុដើមដើម្បីធ្វើឱ្យវត្ថុរង។ +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qty ដែលបានបម្រុងទុកសម្រាប់កិច្ចសន្យារង: បរិមាណវត្ថុធាតុដើមដើម្បីធ្វើឱ្យវត្ថុរង។ apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,ធាតុវ៉ារ្យង់ {0} រួចហើយដែលមានគុណលក្ខណៈដូចគ្នា DocType: Item,Hub Publishing Details,ពត៌មានលម្អិតការបោះពុម្ព apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"ការបើក" diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv index 471bd74b38..1673c167f9 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -1572,7 +1572,7 @@ DocType: Sales Invoice,Payment Due Date,ಪಾವತಿ ಕಾರಣ ದಿನ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","ಕಾಯ್ದಿರಿಸಲಾಗಿದೆ ಪ್ರಮಾಣ: ಪ್ರಮಾಣ ಮಾರಾಟ ಆದೇಶ , ಆದರೆ ಈಡೇರಿಸಿಲ್ಲ ." DocType: Drug Prescription,Interval UOM,ಮಧ್ಯಂತರ UOM DocType: Customer,"Reselect, if the chosen address is edited after save","ಆಯ್ಕೆ ಮಾಡಿದ ವಿಳಾಸವನ್ನು ಉಳಿಸಿದ ನಂತರ ಸಂಪಾದಿಸಿದ್ದರೆ, ಆಯ್ಕೆ ರದ್ದುಮಾಡಿ" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,ಉಪಗುತ್ತಿಗೆಗಾಗಿ ಕ್ಯೂಟಿ ಕಾಯ್ದಿರಿಸಲಾಗಿದೆ: ಉಪಕೋಟ್ರಾಕ್ಟ್ ವಸ್ತುಗಳನ್ನು ತಯಾರಿಸಲು ಕಚ್ಚಾ ವಸ್ತುಗಳ ಪ್ರಮಾಣ. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,ಉಪಗುತ್ತಿಗೆಗಾಗಿ ಕ್ಯೂಟಿ ಕಾಯ್ದಿರಿಸಲಾಗಿದೆ: ಉಪಕೋಟ್ರಾಕ್ಟ್ ವಸ್ತುಗಳನ್ನು ತಯಾರಿಸಲು ಕಚ್ಚಾ ವಸ್ತುಗಳ ಪ್ರಮಾಣ. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,ಐಟಂ ಭಿನ್ನ {0} ಈಗಾಗಲೇ ಅದೇ ಲಕ್ಷಣಗಳು ಅಸ್ತಿತ್ವದಲ್ಲಿದ್ದರೆ DocType: Item,Hub Publishing Details,ಹಬ್ ಪಬ್ಲಿಷಿಂಗ್ ವಿವರಗಳು apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',ಉದ್ಘಾಟಿಸುತ್ತಿರುವುದು diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv index ab079ea375..91d8198edd 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -1594,7 +1594,7 @@ DocType: Sales Invoice,Payment Due Date,지불 기한 apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","예약 수량 : 수량 판매를 위해 주문,하지만 배달되지 않습니다." DocType: Drug Prescription,Interval UOM,간격 UOM DocType: Customer,"Reselect, if the chosen address is edited after save",저장 후 선택한 주소를 다시 선택한 경우 다시 선택하십시오. -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,외주 용 예약 수량 : 추심 품목을 만들기위한 원자재 수량. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,외주 용 예약 수량 : 추심 품목을 만들기위한 원자재 수량. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,항목 변형 {0} 이미 동일한 속성을 가진 존재 DocType: Item,Hub Publishing Details,허브 출판 세부 정보 apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','열기' diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv index af961b144e..8e2d989f0f 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -1551,7 +1551,7 @@ DocType: Sales Invoice,Payment Due Date,Payment Date ji ber apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Qty Reserve: Quantity ferman da firotanê, lê nehatiye radest kirin." DocType: Drug Prescription,Interval UOM,UOM Interfer DocType: Customer,"Reselect, if the chosen address is edited after save","Hilbijêre, eger navnîşana bijartî piştî tomarkirinê hate guherandin" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qiyama Reserve ji bo Nekokkêşanê: Kêmasiya madeyên xav ji bo çêkirina tiştên subcotracted. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qiyama Reserve ji bo Nekokkêşanê: Kêmasiya madeyên xav ji bo çêkirina tiştên subcontracted. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Babetê Variant {0} ji xwe bi taybetmendiyên xwe heman heye DocType: Item,Hub Publishing Details,Agahdariyên Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Dergeh' diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv index e7349a40d1..6343c61203 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -1576,7 +1576,7 @@ DocType: Sales Invoice,Payment Due Date,ການຊໍາລະເງິນກ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Qty ທີ່ສະຫງວນໄວ້: ຈຳ ນວນທີ່ສັ່ງຊື້, ແຕ່ບໍ່ໄດ້ສົ່ງ." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","ແກ້ໄຂ, ຖ້າຫາກວ່າທີ່ຢູ່ທີ່ເລືອກໄດ້ຖືກແກ້ໄຂຫຼັງຈາກທີ່ບັນທຶກ" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qty ທີ່ສະຫງວນໄວ້ ສຳ ລັບສັນຍາຍ່ອຍ: ປະລິມານວັດຖຸດິບເພື່ອຜະລິດສິນຄ້າຍ່ອຍ. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qty ທີ່ສະຫງວນໄວ້ ສຳ ລັບສັນຍາຍ່ອຍ: ປະລິມານວັດຖຸດິບເພື່ອຜະລິດສິນຄ້າຍ່ອຍ. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,ລາຍການ Variant {0} ມີຢູ່ແລ້ວກັບຄຸນລັກສະນະດຽວກັນ DocType: Item,Hub Publishing Details,Hub Publishing Details apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"ເປີດ ' diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv index a9805ee5d9..2576ccf2e3 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -1575,7 +1575,7 @@ DocType: Sales Invoice,Payment Due Date,Sumokėti iki apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Rezervuotas kiekis: Parduodamas kiekis, bet nepristatytas." DocType: Drug Prescription,Interval UOM,Intervalas UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Iš naujo pažymėkite, jei pasirinktas adresas yra redaguotas po įrašymo" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,"Subrangos užsakytas kiekis: Žaliavų kiekis, iš kurio gaminami subtraktoriai." +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,"Subrangos užsakytas kiekis: Žaliavų kiekis, iš kurio gaminami subtraktoriai." apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Prekė variantas {0} jau egzistuoja su tais pačiais atributais DocType: Item,Hub Publishing Details,Hub Publishing duomenys apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Atidarymas" diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv index 12348de84d..dfc007654c 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -1572,7 +1572,7 @@ DocType: Sales Invoice,Payment Due Date,Maksājuma Due Date apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserved Daudzums: pasūtīts pārdod daudzums, bet nav sniegusi." DocType: Drug Prescription,Interval UOM,Intervāls UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Atkārtoti atlasiet, ja pēc saglabāšanas izvēlētā adrese tiek rediģēta" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,"Rezervētais daudzums apakšlīgumam: Izejvielu daudzums, lai izgatavotu apakšsavilkumus." +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,"Rezervētais daudzums apakšlīgumam: Izejvielu daudzums, lai izgatavotu apakšsavilkumus." apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Postenis Variant {0} jau eksistē ar tiem pašiem atribūtiem DocType: Item,Hub Publishing Details,Hub Publicēšanas informācija apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Atklāšana" diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv index 51f272675b..7101b25cc7 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -1563,7 +1563,7 @@ DocType: Sales Invoice,Payment Due Date,Плаќање најдоцна до Д apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Резервирана количина: Количина нарачана за продажба, но не е доставена." DocType: Drug Prescription,Interval UOM,Интервал UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Ресетирај, ако избраната адреса е изменета по зачувување" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Резервирана количина за подизведувач: Количина на суровини за да се направат супструктивни производи. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Резервирана количина за подизведувач: Количина на суровини за да се направат супструктивни производи. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Ставка Варијанта {0} веќе постои со истите атрибути DocType: Item,Hub Publishing Details,Детали за објавување на центар apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Отворање' diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv index 33db47fb4a..495dca46b5 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -1544,7 +1544,7 @@ DocType: Sales Invoice,Payment Due Date,പെയ്മെന്റ് നിശ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","റിസർവ്വ് ചെയ്ത ക്യൂട്ടി: അളവ് വിൽപ്പനയ്ക്ക് ഓർഡർ ചെയ്തു, പക്ഷേ വിതരണം ചെയ്തിട്ടില്ല." DocType: Drug Prescription,Interval UOM,ഇടവേള UOM DocType: Customer,"Reselect, if the chosen address is edited after save","തിരഞ്ഞെടുത്തതിനുശേഷം തിരഞ്ഞെടുത്ത വിലാസം എഡിറ്റുചെയ്തിട്ടുണ്ടെങ്കിൽ, തിരഞ്ഞെടുപ്പ് മാറ്റുക" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,സബ് കോൺ‌ട്രാക്റ്റിനായി റിസർവ്വ് ചെയ്‌ത ക്യൂട്ടി: സബ്‌കോട്രാക്റ്റ് ഇനങ്ങൾ‌ നിർമ്മിക്കുന്നതിനുള്ള അസംസ്കൃത വസ്തുക്കളുടെ അളവ്. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,സബ് കോൺ‌ട്രാക്റ്റിനായി റിസർവ്വ് ചെയ്‌ത ക്യൂട്ടി: സബ്‌കോട്രാക്റ്റ് ഇനങ്ങൾ‌ നിർമ്മിക്കുന്നതിനുള്ള അസംസ്കൃത വസ്തുക്കളുടെ അളവ്. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,ഇനം വേരിയന്റ് {0} ഇതിനകം അതേ ആട്രിബ്യൂട്ടുകളുമുള്ള നിലവിലുണ്ട് DocType: Item,Hub Publishing Details,ഹബ് പബ്ലിഷിംഗ് വിശദാംശങ്ങൾ apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','തുറക്കുന്നു' diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv index 36df9111b4..8ad9738648 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -1557,7 +1557,7 @@ DocType: Sales Invoice,Payment Due Date,पैसे भरण्याची apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","आरक्षित मात्रा: विक्रीचे आदेश दिले, पण दिले नाहीत." DocType: Drug Prescription,Interval UOM,मध्यांतर UOM DocType: Customer,"Reselect, if the chosen address is edited after save","निवड रद्द केलेला पत्ता जतन केल्यानंतर संपादित केले असल्यास, निवड रद्द करा" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,सब कॉन्ट्रॅक्टसाठी राखीव क्वाटीटी: सबकोट्रेक्ट केलेल्या वस्तू तयार करण्यासाठी कच्च्या मालाचे प्रमाण. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,सब कॉन्ट्रॅक्टसाठी राखीव क्वाटीटी: सबकोट्रेक्ट केलेल्या वस्तू तयार करण्यासाठी कच्च्या मालाचे प्रमाण. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,आयटम व्हेरियंट {0} आधीच समान गुणधर्म अस्तित्वात आहे DocType: Item,Hub Publishing Details,हब प्रकाशन तपशील apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','उघडणे' diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv index 0db0aa9095..e69a6aa313 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -1576,7 +1576,7 @@ DocType: Sales Invoice,Payment Due Date,Tarikh Pembayaran apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Dicadangkan Qty: Kuantiti yang dipesan untuk dijual, tetapi tidak dihantar." DocType: Drug Prescription,Interval UOM,Selang UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Pilih semula, jika alamat yang dipilih disunting selepas menyimpan" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Dicadangkan Qty untuk Subkontrak: Kuantiti bahan mentah untuk membuat item subcotracted. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Dicadangkan Qty untuk Subkontrak: Kuantiti bahan mentah untuk membuat item subcontracted. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Perkara Variant {0} telah wujud dengan sifat-sifat yang sama DocType: Item,Hub Publishing Details,Butiran Penerbitan Hab apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Pembukaan' diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv index 1b4f97e186..cdb2a5ef11 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -1576,7 +1576,7 @@ DocType: Sales Invoice,Payment Due Date,ငွေပေးချေမှုရ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",ချုပ်ထိန်းထားသည်အရည်အတွက်: QUANTITY ရောင်းရန်အမိန့်ထုတ်ပေမယ့်လက်သို့အပ်ဘူး။ DocType: Drug Prescription,Interval UOM,ကြားကာလ UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Reselect, ရွေးကောက်တော်မူသောလိပ်စာမှတပါးပြီးနောက် edited လျှင်" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Subcontract အဘို့အချုပ်ထိန်းထားသည်အရည်အတွက်: subcotracted ပစ္စည်းများလုပ်ကုန်ကြမ်းအရေအတွက်။ +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Subcontract အဘို့အချုပ်ထိန်းထားသည်အရည်အတွက်: subcontracted ပစ္စည်းများလုပ်ကုန်ကြမ်းအရေအတွက်။ apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,item Variant {0} ပြီးသားအတူတူ Attribute တွေနှင့်အတူတည်ရှိမှု့ DocType: Item,Hub Publishing Details,hub ထုတ်ဝေရေးအသေးစိတ် apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','' ဖွင့်ပွဲ '' diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index c14507c783..0f62213936 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -1591,7 +1591,7 @@ DocType: Sales Invoice,Payment Due Date,Betaling Vervaldatum apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Gereserveerde Hoeveelheid: Aantal toegewezen aan verkoop, maar nog niet geleverd." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Selecteer opnieuw, als het gekozen adres is bewerkt na opslaan" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Gereserveerde hoeveelheid voor onderaanneming: hoeveelheid grondstoffen voor het maken van artikelen die zijn ondergetrokken. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Gereserveerde hoeveelheid voor onderaanneming: hoeveelheid grondstoffen voor het maken van artikelen die zijn ondergetrokken. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Artikel Variant {0} bestaat al met dezelfde kenmerken DocType: Item,Hub Publishing Details,Hub publicatie details apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Opening' diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv index 0995304fb2..89c4dc1a54 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -1576,7 +1576,7 @@ DocType: Sales Invoice,Payment Due Date,Betalingsfrist apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reservert antall: Antall bestilt for salg, men ikke levert." DocType: Drug Prescription,Interval UOM,Intervall UOM DocType: Customer,"Reselect, if the chosen address is edited after save",Velg hvis den valgte adressen redigeres etter lagre -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Reservert antall for underleveranser: Råvaremengde for å lage underleverandørvarer. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Reservert antall for underleveranser: Råvaremengde for å lage underleverandørvarer. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Sak Variant {0} finnes allerede med samme attributtene DocType: Item,Hub Publishing Details,Hub Publishing Detaljer apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Opening" diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv index 97dc1fbd25..4eff151103 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -1595,7 +1595,7 @@ DocType: Sales Invoice,Payment Due Date,Termin Płatności apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.", DocType: Drug Prescription,Interval UOM,Interwał UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Ponownie wybierz, jeśli wybrany adres jest edytowany po zapisaniu" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Zarezerwowana ilość na zlecenie podwykonawstwa: ilość surowców do produkcji artykułów objętych subkontraktami. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Zarezerwowana ilość na zlecenie podwykonawstwa: ilość surowców do produkcji artykułów objętych subkontraktami. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Pozycja Wersja {0} istnieje już z samymi atrybutami DocType: Item,Hub Publishing Details,Szczegóły publikacji wydawnictwa Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Otwarcie" diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv index 5b0bc317f5..faeaed9032 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -1550,7 +1550,7 @@ DocType: Sales Invoice,Payment Due Date,د پیسو له امله نېټه apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",خوندي مقدار: مقدار د پلور لپاره امر کړی ، مګر تحویلی شوی نه دی. DocType: Drug Prescription,Interval UOM,د UOM منځګړیتوب DocType: Customer,"Reselect, if the chosen address is edited after save",بې ځایه کړئ، که چیرې غوره شوي پتې د خوندي کولو وروسته سمبال شي -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,د فرعي تړون لپاره خوندي مقدار: د فرعي محصولاتو جوړولو لپاره د خامو موادو مقدار. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,د فرعي تړون لپاره خوندي مقدار: د فرعي محصولاتو جوړولو لپاره د خامو موادو مقدار. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,د قالب variant {0} لا د همدې صفتونو شتون لري DocType: Item,Hub Publishing Details,د هوب د خپرولو توضیحات apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','پرانیستل' diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv index afb38be64c..60c6f95fa1 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -1589,7 +1589,7 @@ DocType: Sales Invoice,Payment Due Date,Data Limite de Pagamento apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Gereserveerd Aantal : Aantal besteld te koop , maar niet geleverd ." DocType: Drug Prescription,Interval UOM,UOM Intervalo DocType: Customer,"Reselect, if the chosen address is edited after save","Reseleccione, se o endereço escolhido for editado após salvar" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qtd reservada para subcontratação: quantidade de matérias-primas para fazer itens subcotados. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qtd reservada para subcontratação: quantidade de matérias-primas para fazer itens subcotados. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,A Variante do Item {0} já existe com mesmos atributos DocType: Item,Hub Publishing Details,Detalhes da publicação do hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Abertura' diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv index bfa5f579b9..1678cba0f6 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -1594,7 +1594,7 @@ DocType: Sales Invoice,Payment Due Date,Data scadentă de plată apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Rezervate Cantitate: Cantitatea comandat de vânzare, dar nu livrat." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save",Resetați dacă adresa editată este editată după salvare -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Cantitate rezervată pentru subcontract: cantitate de materii prime pentru a face obiecte subcontractate. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Cantitate rezervată pentru subcontract: cantitate de materii prime pentru a face obiecte subcontractate. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Postul Varianta {0} există deja cu aceleași atribute DocType: Item,Hub Publishing Details,Detalii privind publicarea Hubului apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Deschiderea" diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 3d40fa5645..d9f434070e 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1594,7 +1594,7 @@ DocType: Sales Invoice,Payment Due Date,Дата платежа apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Защищены Кол-во: Количество приказал на продажу, но не поставлены." DocType: Drug Prescription,Interval UOM,Интервал UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Повторно выберите, если выбранный адрес отредактирован после сохранения" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Зарезервированное кол-во для субконтракта: количество сырья для изготовления субподрядных изделий. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Зарезервированное кол-во для субконтракта: количество сырья для изготовления субподрядных изделий. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Модификация продукта {0} с этими атрибутами уже существует DocType: Item,Hub Publishing Details,Сведения о публикации концентратора apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',«Открывается» diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv index 25da9a8d04..1ff37159a5 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -1546,7 +1546,7 @@ DocType: Sales Invoice,Payment Due Date,ගෙවීම් නියමිත apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",වෙන් කර ඇති Qty: ප්‍රමාණය විකිණීමට ඇණවුම් කළ නමුත් ලබා නොදේ. DocType: Drug Prescription,Interval UOM,UOM හි වේගය DocType: Customer,"Reselect, if the chosen address is edited after save","තෝරාගත් පසු, තෝරාගත් ලිපිනය සුරැකීමෙන් අනතුරුව සංස්කරණය කරනු ලැබේ" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,උප කොන්ත්‍රාත්තු සඳහා වෙන් කර ඇති Qty: උප කොන්ත්‍රාත් අයිතම සෑදීම සඳහා අමුද්‍රව්‍ය ප්‍රමාණය. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,උප කොන්ත්‍රාත්තු සඳහා වෙන් කර ඇති Qty: උප කොන්ත්‍රාත් අයිතම සෑදීම සඳහා අමුද්‍රව්‍ය ප්‍රමාණය. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,අයිතමය ප්රභේද්යයක් {0} දැනටමත් එම ලක්ෂණ සහිත පවතී DocType: Item,Hub Publishing Details,තොරතුරු මධ්යස්ථානය තොරතුරු විස්තර apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','විවෘත' diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv index cb6990b5c5..6c26ccc7a9 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -1592,7 +1592,7 @@ DocType: Sales Invoice,Payment Due Date,Splatné dňa apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserved Množství: Množství objednal k prodeji, ale není doručena." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Znovu zvoľte, ak je zvolená adresa upravená po uložení" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Vyhradené množstvo pre subdodávky: Množstvo surovín na výrobu subdodávateľských položiek. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Vyhradené množstvo pre subdodávky: Množstvo surovín na výrobu subdodávateľských položiek. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Variant Položky {0} už existuje s rovnakými vlastnosťami DocType: Item,Hub Publishing Details,Podrobnosti o publikovaní Hubu apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"""Otváranie""" diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv index 9bfbe13640..25389d3ead 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -1575,7 +1575,7 @@ DocType: Sales Invoice,Payment Due Date,Datum zapadlosti apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Količina rezervirana: Količina, naročena za prodajo, vendar ni dobavljena." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Ponovno izberite, če je izbrani naslov urejen po shranjevanju" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Količina za naročila podizvajalcev: Količina surovin za izdelavo odvzetih predmetov. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Količina za naročila podizvajalcev: Količina surovin za izdelavo odvzetih predmetov. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Postavka Variant {0} že obstaja z enakimi atributi DocType: Item,Hub Publishing Details,Podrobnosti o objavi vozlišča apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Odpiranje" diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv index ee7b96eaf8..b9e5278130 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -1556,7 +1556,7 @@ DocType: Sales Invoice,Payment Due Date,Afati i pageses apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Sasia e rezervuar: Sasia e porositur për shitje, por nuk dorëzohet." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Reselect, nëse adresa e zgjedhur është redaktuar pas ruajtjes" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Sasia e rezervuar për nënkontrakt: Sasia e lëndëve të para për të bërë sende nënkontraktuese. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Sasia e rezervuar për nënkontrakt: Sasia e lëndëve të para për të bërë sende nënkontraktuese. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Item Varianti {0} tashmë ekziston me atributet e njëjta DocType: Item,Hub Publishing Details,Detajet e botimit të Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Hapja" diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv index bd2ca7d760..56d5637f19 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -1593,7 +1593,7 @@ DocType: Sales Invoice,Payment Due Date,Плаћање Дуе Дате apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Резервисано Кол : Количина наредио за продају , али не испоручује ." DocType: Drug Prescription,Interval UOM,Интервал УОМ DocType: Customer,"Reselect, if the chosen address is edited after save","Поново изабери, ако је одабрана адреса уређена након чувања" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Количина резервисаног за подуговор: Количине сировина за израду предмета за подухват. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Количина резервисаног за подуговор: Количине сировина за израду предмета за подухват. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Тачка Варијанта {0} већ постоји са истим атрибутима DocType: Item,Hub Publishing Details,Детаљи издавања станице apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Отварање' diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv index 8d81f25e42..f32f72ea84 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -1573,7 +1573,7 @@ DocType: Sales Invoice,Payment Due Date,Förfallodag apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Reserverad antal: Antal som beställts för försäljning, men inte levererat." DocType: Drug Prescription,Interval UOM,Intervall UOM DocType: Customer,"Reselect, if the chosen address is edited after save",Återmarkera om den valda adressen redigeras efter spara -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Reserverad kvantitet för underleverantör: Råvarukvantitet för att tillverka underleverantörer. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Reserverad kvantitet för underleverantör: Råvarukvantitet för att tillverka underleverantörer. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Punkt Variant {0} finns redan med samma attribut DocType: Item,Hub Publishing Details,Hub Publishing Detaljer apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Öppna" diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv index cfe805f434..9641dfc765 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -1560,7 +1560,7 @@ DocType: Sales Invoice,Payment Due Date,Tarehe ya Kutayarisha Malipo apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Qty iliyohifadhiwa: Wingi imeamuru kuuzwa, lakini haijafikishwa." DocType: Drug Prescription,Interval UOM,Muda wa UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Pitia tena, ikiwa anwani iliyochaguliwa imebadilishwa baada ya kuokoa" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qty iliyohifadhiwa kwa Subcontract: Wingi wa malighafi kutengeneza vitu visivyotengwa. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qty iliyohifadhiwa kwa Subcontract: Wingi wa malighafi kutengeneza vitu visivyotengwa. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Tofauti ya kipengee {0} tayari ipo na sifa sawa DocType: Item,Hub Publishing Details,Maelezo ya Uchapishaji wa Hub apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Kufungua' diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv index 34c430afa8..bde61f1188 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -1565,7 +1565,7 @@ DocType: Sales Invoice,Payment Due Date,கொடுப்பனவு கா apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","பாதுகாக்கப்பட்டவை அளவு: அளவு விற்பனை உத்தரவிட்டார் , ஆனால் கொடுத்தது இல்லை ." DocType: Drug Prescription,Interval UOM,இடைவெளி UOM DocType: Customer,"Reselect, if the chosen address is edited after save","தேர்ந்தெடுக்கப்பட்ட முகவரி சேமிக்கப்பட்ட பிறகு திருத்தப்பட்டால், தேர்வுநீக்கம் செய்யவும்" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,துணை ஒப்பந்தத்திற்கான ஒதுக்கப்பட்ட Qty: துணை ஒப்பந்தம் செய்யப்பட்ட பொருட்களை உருவாக்க மூலப்பொருட்களின் அளவு. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,துணை ஒப்பந்தத்திற்கான ஒதுக்கப்பட்ட Qty: துணை ஒப்பந்தம் செய்யப்பட்ட பொருட்களை உருவாக்க மூலப்பொருட்களின் அளவு. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,பொருள் மாற்று {0} ஏற்கனவே அதே பண்புகளை கொண்ட உள்ளது DocType: Item,Hub Publishing Details,ஹப் பப்ளிஷிங் விவரங்கள் apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','திறந்து' diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv index b0a8ade164..14b40bf013 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -1545,7 +1545,7 @@ DocType: Sales Invoice,Payment Due Date,చెల్లింపు గడువ apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","రిజర్వు చేయబడిన Qty: పరిమాణం అమ్మకానికి ఆర్డర్ చేయబడింది, కానీ పంపిణీ చేయబడలేదు." DocType: Drug Prescription,Interval UOM,విరామం UOM DocType: Customer,"Reselect, if the chosen address is edited after save","ఎంపిక చేసిన చిరునామా సేవ్ అయిన తర్వాత సవరించబడితే, ఎంపికను తీసివేయండి" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,సబ్ కాంట్రాక్ట్ కోసం రిజర్వు చేయబడిన క్యూటి: సబ్‌కట్రాక్టెడ్ వస్తువులను తయారు చేయడానికి ముడి పదార్థాల పరిమాణం. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,సబ్ కాంట్రాక్ట్ కోసం రిజర్వు చేయబడిన క్యూటి: సబ్‌కట్రాక్టెడ్ వస్తువులను తయారు చేయడానికి ముడి పదార్థాల పరిమాణం. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,అంశం వేరియంట్ {0} ఇప్పటికే అదే గుణ ఉంది DocType: Item,Hub Publishing Details,హబ్ ప్రచురణ వివరాలు apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','ప్రారంభిస్తున్నాడు' diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv index 3b32053d7d..ec2e77d0aa 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -1595,7 +1595,7 @@ DocType: Sales Invoice,Payment Due Date,วันที่ครบกำหน apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",ลิขสิทธิ์ จำนวน: จำนวน ที่สั่งซื้อ สำหรับการขาย แต่ ไม่ได้ส่ง DocType: Drug Prescription,Interval UOM,ช่วง UOM DocType: Customer,"Reselect, if the chosen address is edited after save",เลือกใหม่ถ้าที่อยู่ที่เลือกถูกแก้ไขหลังจากบันทึกแล้ว -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,ปริมาณที่สงวนไว้สำหรับการรับเหมาช่วง: ปริมาณวัตถุดิบเพื่อทำรายการย่อย +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,ปริมาณที่สงวนไว้สำหรับการรับเหมาช่วง: ปริมาณวัตถุดิบเพื่อทำรายการย่อย apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,รายการตัวแปร {0} อยู่แล้วที่มีลักษณะเดียวกัน DocType: Item,Hub Publishing Details,รายละเอียด Hub Publishing apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','กำลังเปิด' diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index 7a5cc76b46..a35f38c33c 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -1724,7 +1724,7 @@ DocType: Sales Invoice,Payment Due Date,Son Ödeme Tarihi apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Ayrılan Miktar: Satış için sipariş edilen, ancak teslim edilmeyen miktar." DocType: Drug Prescription,Interval UOM,Aralık UOM'sı DocType: Customer,"Reselect, if the chosen address is edited after save",Seçilen adres kaydedildikten sonra değiştirilirse yeniden seç -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Ayrılmış Taşeron Miktarı: Taşeron ürün yapmak için hammadde miktarı. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Ayrılmış Taşeron Miktarı: Taşeron ürün yapmak için hammadde miktarı. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Öğe Variant {0} zaten aynı özelliklere sahip bulunmaktadır DocType: Item,Hub Publishing Details,Hub Yayınlama Ayrıntıları apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Açılış' diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv index 7e1eb60b3b..d7f415f839 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -1574,7 +1574,7 @@ DocType: Sales Invoice,Payment Due Date,Дата платежу apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Кількість зарезервованих: кількість замовлена на продаж, але не доставлена." DocType: Drug Prescription,Interval UOM,Інтервал УОМ DocType: Customer,"Reselect, if the chosen address is edited after save","Змініть вибір, якщо обрана адреса буде відредагована після збереження" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,"Кількість зарезервованих для субпідряду: кількість сировини для виготовлення предметів, що віднімаються на підряд." +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,"Кількість зарезервованих для субпідряду: кількість сировини для виготовлення предметів, що віднімаються на підряд." apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Вже існує варіант позиції {0} з такими атрибутами DocType: Item,Hub Publishing Details,Публікація концентратора apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"""Відкривається""" diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv index 5187789d8e..208fa878dd 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -1548,7 +1548,7 @@ DocType: Sales Invoice,Payment Due Date,ادائیگی کی وجہ سے تاری apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",محفوظ مقدار: مقدار فروخت کے لئے آرڈر کی گئی ، لیکن فراہم نہیں کی گئی۔ DocType: Drug Prescription,Interval UOM,انٹرا UOM DocType: Customer,"Reselect, if the chosen address is edited after save",منتخب کرنے کے بعد، منتخب کردہ ایڈریس کو بچانے کے بعد میں ترمیم کیا جاتا ہے -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,ذیلی معاہدے کے لئے محفوظ مقدار: سب کوٹریکٹ اشیاء بنانے کے لئے خام مال کی مقدار۔ +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,ذیلی معاہدے کے لئے محفوظ مقدار: سب کوٹریکٹ اشیاء بنانے کے لئے خام مال کی مقدار۔ apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,آئٹم مختلف {0} پہلے ہی صفات کے ساتھ موجود DocType: Item,Hub Publishing Details,ہب پبلشنگ کی تفصیلات apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',افتتاحی' diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv index bee2f95f05..e3b0b63655 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -1558,7 +1558,7 @@ DocType: Sales Invoice,Payment Due Date,To'lov sanasi apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Savdo zahirasi: Sotish uchun buyurtma berilgan, ammo etkazib berilmagan." DocType: Drug Prescription,Interval UOM,Intervalli UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Tanlangan manzil saqlashdan so'ng tahrirlangan taqdirda, qayta belgilanadi" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Subtrudrat uchun ajratilgan Qty: subkartralangan buyumlarni tayyorlash uchun xom ashyo miqdori. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Subtrudrat uchun ajratilgan Qty: subkartralangan buyumlarni tayyorlash uchun xom ashyo miqdori. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Mavzu Variant {0} allaqachon bir xil atributlarga ega DocType: Item,Hub Publishing Details,Hub nashriyot tafsilotlari apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',"Ochilish" diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv index ccaa0de4d1..b6eb3d65fe 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -1575,7 +1575,7 @@ DocType: Sales Invoice,Payment Due Date,Thanh toán đáo hạo apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.","Dành Số lượng: Số lượng đặt hàng để bán, nhưng không chuyển giao." DocType: Drug Prescription,Interval UOM,Interval UOM DocType: Customer,"Reselect, if the chosen address is edited after save","Chọn lại, nếu địa chỉ đã chọn được chỉnh sửa sau khi lưu" -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,Qty dành riêng cho hợp đồng thầu phụ: Số lượng nguyên liệu thô để làm các mặt hàng được thu nhỏ. +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Qty dành riêng cho hợp đồng thầu phụ: Số lượng nguyên liệu thô để làm các mặt hàng được thu nhỏ. apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,Biến thể mẫu hàng {0} đã tồn tại với cùng một thuộc tính DocType: Item,Hub Publishing Details,Chi tiết Xuất bản Trung tâm apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening','Đang mở' diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index 93c5e2d48a..1c7985f382 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -1585,7 +1585,7 @@ DocType: Sales Invoice,Payment Due Date,付款到期日 apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",版权所有数量:订购数量出售,但未交付。 DocType: Drug Prescription,Interval UOM,间隔UOM DocType: Customer,"Reselect, if the chosen address is edited after save",重新选择,如果所选地址在保存后被编辑 -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,分包合同的保留数量:制作分项目的原材料数量。 +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,分包合同的保留数量:制作分项目的原材料数量。 apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,项目变体{0}已经具有相同属性的存在 DocType: Item,Hub Publishing Details,集线器发布细节 apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',“打开” diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv index 7fb0b225cb..7f83dfb7b4 100644 --- a/erpnext/translations/zh_tw.csv +++ b/erpnext/translations/zh_tw.csv @@ -1450,7 +1450,7 @@ apps/erpnext/erpnext/stock/page/stock_balance/stock_balance.js,Projected Qty,預 apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,"Reserved Qty: Quantity ordered for sale, but not delivered.",保留數量:訂購數量待出售,但尚未交付。 DocType: Drug Prescription,Interval UOM,間隔UOM DocType: Customer,"Reselect, if the chosen address is edited after save",重新選擇,如果所選地址在保存後被編輯 -apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.,分包合同的保留數量:製作分項目的原材料數量。 +apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.js,Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,分包合同的保留數量:製作分項目的原材料數量。 apps/erpnext/erpnext/stock/doctype/item/item.js,Item Variant {0} already exists with same attributes,項目變種{0}已經具有相同屬性的存在 DocType: Item,Hub Publishing Details,Hub發布細節 apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py,'Opening',“開放” From 5c9d92eabf6cc06eb745ca77ca4d2374650ddab8 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 30 Oct 2019 14:18:16 +0530 Subject: [PATCH 032/131] fix: On Specific case if no item code in name (#19419) --- erpnext/manufacturing/doctype/bom/bom.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 225ae29429..c15b52ea38 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -39,9 +39,11 @@ class BOM(WebsiteGenerator): names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)] # split by (-) if cancelled - names = [cint(name.split('-')[-1]) for name in names] - - idx = max(names) + 1 + if names: + names = [cint(name.split('-')[-1]) for name in names] + idx = max(names) + 1 + else: + idx = 1 else: idx = 1 From 486a8f67a4b0160f6031fa483f6916b43630aef3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Oct 2019 14:22:49 +0530 Subject: [PATCH 033/131] fix: not able to select the project in the work order --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 1789a1f883..cdbce33e1f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -342,7 +342,7 @@ frappe.ui.form.on("Work Order", { }, project: function(frm) { - if(!erpnext.in_production_item_onchange) { + if(!erpnext.in_production_item_onchange && !frm.doc.bom_no) { frm.trigger("production_item"); } }, From 7638788c2f3f3fab80f2ef782bf843b17f428b46 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 30 Oct 2019 14:23:41 +0530 Subject: [PATCH 034/131] fix: calculate pending leaves (#19411) --- .../hr/doctype/leave_application/leave_application.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 97de40ffee..b73028e50a 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -503,14 +503,17 @@ def get_leave_allocation_records(employee, date, leave_type=None): def get_pending_leaves_for_period(employee, leave_type, from_date, to_date): ''' Returns leaves that are pending approval ''' - return frappe.db.get_value("Leave Application", + leaves = frappe.get_all("Leave Application", filters={ "employee": employee, "leave_type": leave_type, - "from_date": ("<=", from_date), - "to_date": (">=", to_date), "status": "Open" - }, fieldname=['SUM(total_leave_days)']) or flt(0) + }, + or_filters={ + "from_date": ["between", (from_date, to_date)], + "to_date": ["between", (from_date, to_date)] + }, fields=['SUM(total_leave_days) as leaves'])[0] + return leaves['leaves'] if leaves['leaves'] else 0.0 def get_remaining_leaves(allocation, leaves_taken, date, expiry): ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry ''' From 83c7b5b44b40d3a61ec5e9f36dcfec88949a0515 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 30 Oct 2019 14:25:50 +0530 Subject: [PATCH 035/131] fix(bom): maintain a default bom for an item (#19407) --- erpnext/manufacturing/doctype/bom/bom.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c15b52ea38..c849f5b7f2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -292,7 +292,8 @@ class BOM(WebsiteGenerator): return valuation_rate def manage_default_bom(self): - """ Uncheck others if current one is selected as default, + """ Uncheck others if current one is selected as default or + check the current one as default if it the only bom for the selected item, update default bom in item master """ if self.is_default and self.is_active: @@ -301,6 +302,9 @@ class BOM(WebsiteGenerator): item = frappe.get_doc("Item", self.item) if item.default_bom != self.name: frappe.db.set_value('Item', self.item, 'default_bom', self.name) + elif not frappe.db.exists(dict(doctype='BOM', docstatus=1, item=self.item, is_default=1)) \ + and self.is_active: + frappe.db.set(self, "is_default", 1) else: frappe.db.set(self, "is_default", 0) item = frappe.get_doc("Item", self.item) From d38f5374cb8eb290ff4ed1fbccd940de3f2d25d8 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 30 Oct 2019 14:43:58 +0530 Subject: [PATCH 036/131] chore: moved email digest to long job --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5c61874f50..b1855ec9bb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -283,7 +283,6 @@ scheduler_events = { ], "daily": [ "erpnext.stock.reorder_item.reorder_item", - "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.support.doctype.issue.issue.auto_close_tickets", "erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity", "erpnext.controllers.accounts_controller.update_invoice_status", @@ -306,6 +305,7 @@ scheduler_events = { "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status" ], "daily_long": [ + "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", "erpnext.hr.utils.generate_leave_encashment" From 2e01573a40571b90be066ab87e2706d7e132c258 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Oct 2019 16:22:30 +0530 Subject: [PATCH 037/131] fix: item price stock report not working --- erpnext/stock/report/item_price_stock/item_price_stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index e539aff59c..a0783da3aa 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -89,7 +89,7 @@ def get_item_price_qty_data(filters): {conditions}""" .format(conditions=conditions), filters, as_dict=1) - price_list_names = list(set([frappe.db.escape(item.price_list_name) for item in item_results])) + price_list_names = list(set([item.price_list_name for item in item_results])) buying_price_map = get_price_map(price_list_names, buying=1) selling_price_map = get_price_map(price_list_names, selling=1) From d8b64cd19963978c8ced612e4b23a6a87cf3b4e8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Oct 2019 16:27:58 +0530 Subject: [PATCH 038/131] replaced frappe.db.sql with frappe.get_all --- .../item_price_stock/item_price_stock.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index a0783da3aa..5296211fae 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -129,17 +129,15 @@ def get_price_map(price_list_names, buying=0, selling=0): rate_key = "Buying Rate" if buying else "Selling Rate" price_list_key = "Buying Price List" if buying else "Selling Price List" - price_list_condition = " and buying=1" if buying else " and selling=1" - pricing_details = frappe.db.sql(""" - select - name,price_list,price_list_rate - from - `tabItem Price` - where - name in ({price_list_names}) {price_list_condition} - """.format(price_list_names=', '.join(['%s']*len(price_list_names)), - price_list_condition=price_list_condition), price_list_names, as_dict=1) + filters = {"name": ("in", price_list_names)} + if buying: + filters["buying"] = 1 + else: + filters["selling"] = 1 + + pricing_details = frappe.get_all("Item Price", + fields = ["name", "price_list", "price_list_rate"], filters=filters) for d in pricing_details: name = d["name"] From 6bd6039496987a987e43352f7a0b4fdc4a910e1a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 30 Oct 2019 17:37:22 +0530 Subject: [PATCH 039/131] Revert "fix: patch, item tax template showing 'Untitled' in the name" --- .../move_item_tax_to_item_tax_template.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 6769144aeb..412f32030a 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -1,6 +1,5 @@ import frappe import json -from frappe.model.naming import make_autoname from six import iteritems def execute(): @@ -8,6 +7,7 @@ def execute(): return old_item_taxes = {} item_tax_templates = {} + rename_template_to_untitled = [] for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1): old_item_taxes.setdefault(d.item_code, []) @@ -34,7 +34,7 @@ def execute(): for d in old_item_taxes[item_code]: item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, + item_tax_template_name = get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, item_code) # update the item tax table @@ -53,19 +53,26 @@ def execute(): for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` where ifnull(item_tax_rate, '') not in ('', '{{}}')""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) - item_tax_template = get_item_tax_template(item_tax_templates, + item_tax_template = get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, d.item_code, d.parent) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template) + idx = 1 + for oldname in rename_template_to_untitled: + frappe.rename_doc("Item Tax Template", oldname, "Untitled {}".format(idx)) + idx += 1 + settings = frappe.get_single("Accounts Settings") settings.add_taxes_from_item_tax_template = 0 settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None): +def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, item_code, parent=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: + if not parent: + rename_template_to_untitled.append(template) return template # if no item tax template found, create one @@ -90,11 +97,5 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=No item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_templates[item_tax_template.title][tax_type] = tax_rate - - try: - item_tax_template.save() - except frappe.DuplicateEntryError: - item_tax_template.name = make_autoname(item_tax_template.title + "/.###") - item_tax_template.save() - + item_tax_template.save() return item_tax_template.name From 1e754b161c3c091579fcb74716434bdbb2460674 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 30 Oct 2019 18:33:44 +0530 Subject: [PATCH 040/131] fix: Fetching catched meta and removed description fetch from Search Fields Description is conditionally fetched and also used in WHERE clause, that is maintained. Improved naming --- erpnext/controllers/queries.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index ac89588978..a9e50bab5a 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -153,14 +153,17 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals conditions = [] #Get searchfields from meta and use in Item Link field query - meta = frappe.get_meta("Item") + meta = frappe.get_meta("Item", cached=True) searchfields = meta.get_search_fields() - fields = [f for f in searchfields if not f in ["name", "item_group", "description"]] - fields = ", ".join(fields) + if "description" in searchfields: + searchfields.remove("description") - searchfields = searchfields + [f for f in [searchfield or "name", "item_code", "item_group", "item_name"] - if not f in searchfields] + columns = [field for field in searchfields if not field in ["name", "item_group", "description"]] + columns = ", ".join(columns) + + searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"] + if not field in searchfields] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) description_cond = '' @@ -174,7 +177,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals tabItem.item_group, if(length(tabItem.description) > 40, \ concat(substr(tabItem.description, 1, 40), "..."), description) as description, - {fields} + {columns} from tabItem where tabItem.docstatus < 2 and tabItem.has_variants=0 @@ -190,7 +193,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals name, item_name limit %(start)s, %(page_len)s """.format( key=searchfield, - fields=fields, + columns=columns, scond=searchfields, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'), From a11e7388017b0f5d939871949839add7fb0a360e Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 31 Oct 2019 15:55:03 +0530 Subject: [PATCH 041/131] feat: Stock value and account balance sync. (#19233) * feat: Allow user to sync stock_value and account_balance jv if perpetual inventory is checked * fix(test): Sales Invoice * fix(test): Purchase Invoice * fix(test): Delivery Note * fix: more test_case * fix(test): Stock Entry * fix(test): Purchase Receipt * fix(more-test): Stock Entries * fix(more-test): Sales Invoice and Delivery Note * fix: tests for delivery note * fix: tests for stock reconciliation * refactor: stock and account balance function * fix(more-test): Warehouse * fix(test): Landed Cost Voucher * fix: changes requested --- .../accounts/doctype/account/test_account.py | 2 +- .../loyalty_program/test_loyalty_program.py | 2 + .../purchase_invoice/test_purchase_invoice.py | 54 ++--- erpnext/accounts/doctype/sales_invoice/pos.py | 23 +- .../doctype/sales_invoice/sales_invoice.py | 1 - .../doctype/sales_invoice/test_records.json | 3 - .../sales_invoice/test_sales_invoice.py | 161 +++++++++----- .../shipping_rule/test_shipping_rule.py | 10 +- erpnext/accounts/general_ledger.py | 40 +++- erpnext/accounts/utils.py | 28 ++- erpnext/controllers/stock_controller.py | 35 --- .../production_plan/test_production_plan.py | 2 + .../doctype/work_order/test_work_order.py | 2 +- erpnext/setup/doctype/company/test_company.py | 18 +- .../setup/doctype/company/test_records.json | 11 + erpnext/stock/__init__.py | 2 +- erpnext/stock/doctype/batch/test_batch.py | 5 +- .../delivery_note/test_delivery_note.py | 208 ++++++++---------- .../item_alternative/test_item_alternative.py | 2 + .../test_landed_cost_voucher.py | 90 ++++---- .../purchase_receipt/test_purchase_receipt.py | 142 ++++++++---- .../purchase_receipt/test_records.json | 34 --- .../stock/doctype/serial_no/test_serial_no.py | 3 + .../doctype/stock_entry/test_stock_entry.py | 104 +++++---- .../test_stock_reconciliation.py | 39 ++-- .../stock/doctype/warehouse/test_records.json | 6 - .../stock/doctype/warehouse/test_warehouse.py | 46 +++- 27 files changed, 596 insertions(+), 477 deletions(-) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 4ee55736fe..dc23b2b2d0 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -160,7 +160,7 @@ def _make_test_records(verbose): ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"] ] - for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"]]: + for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]: test_objects = make_test_objects("Account", [{ "doctype": "Account", "account_name": account_name, diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 4a7406e0cb..341884c190 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -8,10 +8,12 @@ import unittest from frappe.utils import today, cint, flt, getdate from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points from erpnext.accounts.party import get_dashboard_info +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestLoyaltyProgram(unittest.TestCase): @classmethod def setUpClass(self): + set_perpetual_inventory(0) # create relevant item, customer, loyalty program, etc create_records() diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6deee38148..b2ad4f4d51 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -10,7 +10,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent from frappe.utils import cint, flt, today, nowdate, add_days import frappe.defaults from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ - test_records as pr_test_records + test_records as pr_test_records, make_purchase_receipt, get_taxes from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.exceptions import InvalidCurrency from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction @@ -57,16 +57,11 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) def test_gl_entries_with_perpetual_inventory(self): - pi = frappe.copy_doc(test_records[1]) - set_perpetual_inventory(1, pi.company) + pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10) self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1) - pi.insert() - pi.submit() self.check_gle_for_pi(pi.name) - set_perpetual_inventory(0, pi.company) - def test_terms_added_after_save(self): pi = frappe.copy_doc(test_records[1]) pi.insert() @@ -196,21 +191,21 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(pi.on_hold, 0) def test_gl_entries_with_perpetual_inventory_against_pr(self): - pr = frappe.copy_doc(pr_test_records[0]) - set_perpetual_inventory(1, pr.company) - self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) - pr.submit() - pi = frappe.copy_doc(test_records[1]) - for d in pi.get("items"): + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,) + + self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) + + pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True") + + for d in pi.items: d.purchase_receipt = pr.name + pi.insert() pi.submit() self.check_gle_for_pi(pi.name) - set_perpetual_inventory(0, pr.company) - def check_gle_for_pi(self, pi): gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s @@ -218,10 +213,10 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - ["_Test Payable - _TC", 0, 720], - ["Stock Received But Not Billed - _TC", 500.0, 0], - ["_Test Account Shipping Charges - _TC", 100.0, 0], - ["_Test Account VAT - _TC", 120.0, 0], + ["Creditors - TCP1", 0, 720], + ["Stock Received But Not Billed - TCP1", 500.0, 0], + ["_Test Account Shipping Charges - TCP1", 100.0, 0], + ["_Test Account VAT - TCP1", 120.0, 0], ]) for i, gle in enumerate(gl_entries): @@ -524,10 +519,9 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertFalse(gle) def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self): - set_perpetual_inventory() pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), - posting_time=frappe.utils.nowtime()) + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, debit_in_account_currency, credit_in_account_currency @@ -548,9 +542,9 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gl_entries[gle.account][2], gle.credit) def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self): - set_perpetual_inventory() + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), - posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1) + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", is_paid=1, company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1") gl_entries = frappe.db.sql("""select account, account_currency, sum(debit) as debit, sum(credit) as credit, debit_in_account_currency, credit_in_account_currency @@ -563,7 +557,7 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gl_entries = dict((d[0], d) for d in [ [pi.credit_to, 250.0, 250.0], [stock_in_hand_account, 250.0, 0.0], - ["Cash - _TC", 0.0, 250.0] + ["Cash - TCP1", 0.0, 250.0] ]) for i, gle in enumerate(gl_entries): @@ -630,6 +624,7 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) def test_rejected_serial_no(self): + set_perpetual_inventory(0) pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1, rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC") @@ -881,7 +876,7 @@ def make_purchase_invoice(**args): pi.is_return = args.is_return pi.return_against = args.return_against pi.is_subcontracted = args.is_subcontracted or "No" - pi.supplier_warehouse = "_Test Warehouse 1 - _TC" + pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pi.append("items", { "item_code": args.item or args.item_code or "_Test Item", @@ -890,14 +885,21 @@ def make_purchase_invoice(**args): "received_qty": args.received_qty or 0, "rejected_qty": args.rejected_qty or 0, "rate": args.rate or 50, + 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC', "conversion_factor": 1.0, "serial_no": args.serial_no, "stock_uom": "_Test UOM", - "cost_center": "_Test Cost Center - _TC", + "cost_center": args.cost_center or "_Test Cost Center - _TC", "project": args.project, "rejected_warehouse": args.rejected_warehouse or "", "rejected_serial_no": args.rejected_serial_no or "" }) + + if args.get_taxes_and_charges: + taxes = get_taxes() + for tax in taxes: + pi.append("taxes", tax) + if not args.do_not_save: pi.insert() if not args.do_not_submit: diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 7d4fc63955..ed45b2cc2c 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -402,14 +402,21 @@ def make_invoice(doc_list={}, email_queue_list={}, customers_list={}): for docs in doc_list: for name, doc in iteritems(docs): if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): - validate_records(doc) - si_doc = frappe.new_doc('Sales Invoice') - si_doc.offline_pos_name = name - si_doc.update(doc) - si_doc.set_posting_time = 1 - si_doc.customer = get_customer_id(doc) - si_doc.due_date = doc.get('posting_date') - name_list = submit_invoice(si_doc, name, doc, name_list) + if isinstance(doc, dict): + validate_records(doc) + si_doc = frappe.new_doc('Sales Invoice') + si_doc.offline_pos_name = name + si_doc.update(doc) + si_doc.set_posting_time = 1 + si_doc.customer = get_customer_id(doc) + si_doc.due_date = doc.get('posting_date') + name_list = submit_invoice(si_doc, name, doc, name_list) + else: + doc.due_date = doc.get('posting_date') + doc.customer = get_customer_id(doc) + doc.set_posting_time = 1 + doc.offline_pos_name = name + name_list = submit_invoice(doc, name, doc, name_list) else: name_list.append(name) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e1256a78d9..5766c9a8d1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -686,7 +686,6 @@ class SalesInvoice(SellingController): def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) - if not gl_entries: gl_entries = self.get_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index 9c8de7d5a2..ebe6e3da8d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -68,8 +68,6 @@ "selling_price_list": "_Test Price List", "territory": "_Test Territory" }, - - { "company": "_Test Company", "conversion_rate": 1.0, @@ -276,7 +274,6 @@ "uom": "_Test UOM 1", "conversion_factor": 1, "stock_uom": "_Test UOM 1" - }, { "cost_center": "_Test Cost Center - _TC", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4f253b69f7..530bd893c0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -20,6 +20,9 @@ from erpnext.stock.doctype.item.test_item import create_item from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction from erpnext.regional.india.utils import get_ewb_data +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice class TestSalesInvoice(unittest.TestCase): def make(self): @@ -550,7 +553,6 @@ class TestSalesInvoice(unittest.TestCase): si.get("taxes")[6].tax_amount = 2 si.insert() - print(si.name) expected_values = [ { @@ -679,56 +681,67 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) def test_pos_gl_entry_with_perpetual_inventory(self): - set_perpetual_inventory() make_pos_profile() - self._insert_purchase_receipt() - pos = copy.deepcopy(test_records[1]) - pos["is_pos"] = 1 - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300}] + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") + + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) + + pos.is_pos = 1 + pos.update_stock = 1 + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50}) + + taxes = get_taxes_and_charges() + pos.taxes = [] + for tax in taxes: + pos.append("taxes", tax) si = frappe.copy_doc(pos) si.insert() si.submit() + self.assertEqual(si.paid_amount, 100.0) - self.assertEqual(si.paid_amount, 600.0) - - self.pos_gl_entry(si, pos, 300) + self.pos_gl_entry(si, pos, 50) def test_pos_change_amount(self): - set_perpetual_inventory() make_pos_profile() - self._insert_purchase_receipt() - pos = copy.deepcopy(test_records[1]) - pos["is_pos"] = 1 - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 340}] + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") - si = frappe.copy_doc(pos) - si.change_amount = 5.0 - si.insert() - si.submit() + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) - self.assertEqual(si.grand_total, 630.0) - self.assertEqual(si.write_off_amount, -5) + pos.is_pos = 1 + pos.update_stock = 1 + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60}) + + pos.change_amount = 5.0 + pos.insert() + pos.submit() + + self.assertEqual(pos.grand_total, 100.0) + self.assertEqual(pos.write_off_amount, -5) def test_make_pos_invoice(self): from erpnext.accounts.doctype.sales_invoice.pos import make_invoice - set_perpetual_inventory() - make_pos_profile() - self._insert_purchase_receipt() + pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) - pos = copy.deepcopy(test_records[1]) - pos["is_pos"] = 1 - pos["update_stock"] = 1 - pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, - {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}] + pos.is_pos = 1 + pos.update_stock = 1 + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50}) + + taxes = get_taxes_and_charges() + pos.taxes = [] + for tax in taxes: + pos.append("taxes", tax) invoice_data = [{'09052016142': pos}] si = make_invoice(invoice_data).get('invoice') @@ -736,16 +749,15 @@ class TestSalesInvoice(unittest.TestCase): sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1}) si = frappe.get_doc('Sales Invoice', sales_invoice[0].name) - self.assertEqual(si.grand_total, 630.0) - self.pos_gl_entry(si, pos, 330) + self.assertEqual(si.grand_total, 100) + + self.pos_gl_entry(si, pos, 50) def test_make_pos_invoice_in_draft(self): from erpnext.accounts.doctype.sales_invoice.pos import make_invoice from erpnext.stock.doctype.item.test_item import make_item - set_perpetual_inventory() - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') if allow_negative_stock: frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) @@ -789,7 +801,7 @@ class TestSalesInvoice(unittest.TestCase): si.name, as_dict=1)[0] self.assertTrue(sle) self.assertEqual([sle.item_code, sle.warehouse, sle.actual_qty], - ["_Test Item", "_Test Warehouse - _TC", -1.0]) + ['_Test FG Item', 'Stores - TCP1', -1.0]) # check gl entries gl_entries = frappe.db.sql("""select account, debit, credit @@ -797,19 +809,19 @@ class TestSalesInvoice(unittest.TestCase): order by account asc, debit asc, credit asc""", si.name, as_dict=1) self.assertTrue(gl_entries) - stock_in_hand = get_inventory_account('_Test Company') - + stock_in_hand = get_inventory_account('_Test Company with perpetual inventory') expected_gl_entries = sorted([ - [si.debit_to, 630.0, 0.0], - [pos["items"][0]["income_account"], 0.0, 500.0], - [pos["taxes"][0]["account_head"], 0.0, 80.0], - [pos["taxes"][1]["account_head"], 0.0, 50.0], + [si.debit_to, 100.0, 0.0], + [pos.items[0].income_account, 0.0, 89.09], + ['Round Off - TCP1', 0.0, 0.01], + [pos.taxes[0].account_head, 0.0, 10.69], + [pos.taxes[1].account_head, 0.0, 0.21], [stock_in_hand, 0.0, abs(sle.stock_value_difference)], - [pos["items"][0]["expense_account"], abs(sle.stock_value_difference), 0.0], - [si.debit_to, 0.0, 300.0], + [pos.items[0].expense_account, abs(sle.stock_value_difference), 0.0], + [si.debit_to, 0.0, 50.0], [si.debit_to, 0.0, cash_amount], - ["_Test Bank - _TC", 300.0, 0.0], - ["Cash - _TC", cash_amount, 0.0] + ["_Test Bank - TCP1", 50, 0.0], + ["Cash - TCP1", cash_amount, 0.0] ]) for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)): @@ -823,9 +835,9 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) - set_perpetual_inventory(0) frappe.db.sql("delete from `tabPOS Profile`") + si.delete() def test_pos_si_without_payment(self): set_perpetual_inventory() @@ -1008,7 +1020,6 @@ class TestSalesInvoice(unittest.TestCase): """ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos se = make_serialized_item() @@ -1023,14 +1034,17 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.get("items")[0].serial_no, dn.get("items")[0].serial_no) def test_return_sales_invoice(self): - set_perpetual_inventory() - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) - actual_qty_0 = get_qty_after_transaction() + actual_qty_0 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") - si = create_sales_invoice(qty=5, rate=500, update_stock=1) + si = create_sales_invoice(qty = 5, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1") + + + actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") + + frappe.db.commit() - actual_qty_1 = get_qty_after_transaction() self.assertEqual(actual_qty_0 - 5, actual_qty_1) # outgoing_rate @@ -1038,10 +1052,9 @@ class TestSalesInvoice(unittest.TestCase): "voucher_no": si.name}, "stock_value_difference") / 5 # return entry - si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1) - - actual_qty_2 = get_qty_after_transaction() + si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1") + actual_qty_2 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") self.assertEqual(actual_qty_1 + 2, actual_qty_2) incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", @@ -1049,7 +1062,7 @@ class TestSalesInvoice(unittest.TestCase): ["incoming_rate", "stock_value_difference"]) self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3))) - stock_in_hand_account = get_inventory_account('_Test Company', si1.items[0].warehouse) + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory', si1.items[0].warehouse) # Check gl entry gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice", @@ -1058,7 +1071,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(gle_warehouse_amount, stock_value_difference) party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice", - "voucher_no": si1.name, "account": "Debtors - _TC", "party": "_Test Customer"}, "credit") + "voucher_no": si1.name, "account": "Debtors - TCP1", "party": "_Test Customer"}, "credit") self.assertEqual(party_credited, 1000) @@ -1066,7 +1079,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(si1.outstanding_amount) self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500) - set_perpetual_inventory(0) def test_discount_on_net_total(self): si = frappe.copy_doc(test_records[2]) @@ -1524,6 +1536,8 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.total_taxes_and_charges, 577.05) self.assertEqual(si.grand_total, 1827.05) + + def test_create_invoice_without_terms(self): si = create_sales_invoice(do_not_save=1) self.assertFalse(si.get('payment_schedule')) @@ -1930,4 +1944,29 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party if against_voucher_type == 'Purchase Invoice': bal = bal * -1 - return bal \ No newline at end of file + return bal + +def get_taxes_and_charges(): + return [{ + "account_head": "_Test Account Excise Duty - TCP1", + "charge_type": "On Net Total", + "cost_center": "Main - TCP1", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "idx": 1, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 12 + }, + { + "account_head": "_Test Account Education Cess - TCP1", + "charge_type": "On Previous Row Amount", + "cost_center": "Main - TCP1", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "idx": 2, + "included_in_print_rate": 1, + "parentfield": "taxes", + "rate": 2, + "row_id": 1 + }] \ No newline at end of file diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py index 582ecb2e16..abc6ab82d3 100644 --- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py @@ -14,13 +14,13 @@ class TestShippingRule(unittest.TestCase): shipping_rule.name = test_records[0].get('name') shipping_rule.get("conditions")[0].from_value = 101 self.assertRaises(FromGreaterThanToError, shipping_rule.insert) - + def test_many_zero_to_values(self): shipping_rule = frappe.copy_doc(test_records[0]) shipping_rule.name = test_records[0].get('name') shipping_rule.get("conditions")[0].to_value = 0 self.assertRaises(ManyBlankToValuesError, shipping_rule.insert) - + def test_overlapping_conditions(self): for range_a, range_b in [ ((50, 150), (0, 100)), @@ -38,6 +38,10 @@ class TestShippingRule(unittest.TestCase): self.assertRaises(OverlappingConditionError, shipping_rule.insert) def create_shipping_rule(shipping_rule_type, shipping_rule_name): + + if frappe.db.exists("Shipping Rule", shipping_rule_name): + return frappe.get_doc("Shipping Rule", shipping_rule_name) + sr = frappe.new_doc("Shipping Rule") sr.account = "_Test Account Shipping Charges - _TC" sr.calculate_based_on = "Net Total" @@ -70,4 +74,4 @@ def create_shipping_rule(shipping_rule_type, shipping_rule_name): }) sr.insert(ignore_permissions=True) sr.submit() - return sr + return sr diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 5c9e93d019..43d9ad6435 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import flt, cstr, cint +from frappe.utils import flt, cstr, cint, comma_and from frappe import _ +from erpnext.accounts.utils import get_stock_and_account_balance from frappe.model.meta import get_field_precision from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions @@ -12,6 +13,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g class ClosedAccountingPeriod(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass +class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): if gl_map: @@ -115,11 +117,9 @@ def check_if_in_list(gle, gl_map, dimensions=None): def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): if not from_repost: - validate_account_for_perpetual_inventory(gl_map) validate_cwip_accounts(gl_map) round_off_debit_credit(gl_map) - for entry in gl_map: make_entry(entry, adv_adj, update_outstanding, from_repost) @@ -127,6 +127,10 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): if not from_repost: validate_expense_against_budget(entry) + if not from_repost: + validate_account_for_perpetual_inventory(gl_map) + + def make_entry(args, adv_adj, update_outstanding, from_repost=False): args.update({"doctype": "GL Entry"}) gle = frappe.get_doc(args) @@ -137,15 +141,31 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.submit() def validate_account_for_perpetual_inventory(gl_map): - if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)) \ - and gl_map[0].voucher_type=="Journal Entry": - aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount - where account_type = 'Stock' and is_group=0""")] + if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): + account_list = [gl_entries.account for gl_entries in gl_map] - for entry in gl_map: - if entry.account in aii_accounts: + aii_accounts = [d.name for d in frappe.get_all("Account", + filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})] + + for account in account_list: + if account not in aii_accounts: + continue + + account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, + gl_map[0].posting_date, gl_map[0].company) + + if gl_map[0].voucher_type=="Journal Entry": + # In case of Journal Entry, there are no corresponding SL entries, + # hence deducting currency amount + account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit) + if account_bal == stock_bal: frappe.throw(_("Account: {0} can only be updated via Stock Transactions") - .format(entry.account), StockAccountInvalidTransaction) + .format(account), StockAccountInvalidTransaction) + + elif account_bal != stock_bal: + frappe.throw(_("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and linked warehouse ({3}). Please create adjustment Journal Entry for amount {4}.") + .format(account_bal, stock_bal, account, comma_and(warehouse_list), stock_bal - account_bal), + StockValueAndAccountBalanceOutOfSync) def validate_cwip_accounts(gl_map): if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \ diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ac69fd3c96..382a89b310 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -13,6 +13,10 @@ from six import iteritems # imported to enable erpnext.accounts.utils.get_account_currency from erpnext.accounts.doctype.account.account import get_account_currency +from erpnext.stock.utils import get_stock_value_on +from erpnext.stock import get_warehouse_account_map + + class FiscalYearError(frappe.ValidationError): pass @frappe.whitelist() @@ -560,23 +564,23 @@ def fix_total_debit_credit(): (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr), (d.diff, d.voucher_type, d.voucher_no)) -def get_stock_and_account_difference(account_list=None, posting_date=None, company=None): - from erpnext.stock.utils import get_stock_value_on - from erpnext.stock import get_warehouse_account_map - +def get_stock_and_account_balance(account=None, posting_date=None, company=None): if not posting_date: posting_date = nowdate() - difference = {} warehouse_account = get_warehouse_account_map(company) - for warehouse, account_data in iteritems(warehouse_account): - if account_data.get('account') in account_list: - account_balance = get_balance_on(account_data.get('account'), posting_date, in_account_currency=False) - stock_value = get_stock_value_on(warehouse, posting_date) - if abs(flt(stock_value) - flt(account_balance)) > 0.005: - difference.setdefault(account_data.get('account'), flt(stock_value) - flt(account_balance)) + account_balance = get_balance_on(account, posting_date, in_account_currency=False) - return difference + related_warehouses = [wh for wh, wh_details in warehouse_account.items() + if wh_details.account == account and not wh_details.is_group] + + total_stock_value = 0.0 + for warehouse in related_warehouses: + value = get_stock_value_on(warehouse, posting_date) + total_stock_value += value + + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses def get_currency_precision(): precision = cint(frappe.db.get_default("currency_precision")) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2d87a98f20..542073ebd7 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -207,41 +207,6 @@ class StockController(AccountsController): reference_doctype=self.doctype, reference_name=self.name)).insert().name - def make_adjustment_entry(self, expected_gle, voucher_obj): - from erpnext.accounts.utils import get_stock_and_account_difference - account_list = [d.account for d in expected_gle] - acc_diff = get_stock_and_account_difference(account_list, - expected_gle[0].posting_date, self.company) - - cost_center = self.get_company_default("cost_center") - stock_adjustment_account = self.get_company_default("stock_adjustment_account") - - gl_entries = [] - for account, diff in acc_diff.items(): - if diff: - gl_entries.append([ - # stock in hand account - voucher_obj.get_gl_dict({ - "account": account, - "against": stock_adjustment_account, - "debit": diff, - "remarks": "Adjustment Accounting Entry for Stock", - }), - - # account against stock in hand - voucher_obj.get_gl_dict({ - "account": stock_adjustment_account, - "against": account, - "credit": diff, - "cost_center": cost_center or None, - "remarks": "Adjustment Accounting Entry for Stock", - }), - ]) - - if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - make_gl_entries(gl_entries) - def check_expense_account(self, item): if not item.get("expense_account"): frappe.throw(_("Expense or Difference account is mandatory for Item {0} as it impacts overall stock value").format(item.item_code)) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index f70c9cc43f..44796417d4 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -11,9 +11,11 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import get_sa from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestProductionPlan(unittest.TestCase): def setUp(self): + set_perpetual_inventory(0) for item in ['Test Production Item 1', 'Subassembly Item 1', 'Raw Material Item 1', 'Raw Material Item 2']: create_item(item, valuation_rate=100) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index d710e57056..ea2e7a96e1 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -17,11 +17,11 @@ from erpnext.manufacturing.doctype.production_plan.test_production_plan import m class TestWorkOrder(unittest.TestCase): def setUp(self): + set_perpetual_inventory(0) self.warehouse = '_Test Warehouse 2 - _TC' self.item = '_Test Item' def check_planned_qty(self): - set_perpetual_inventory(0) planned0 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") or 0 diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 8debef5ff6..8d9c23a37d 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -22,7 +22,7 @@ class TestCompany(unittest.TestCase): company.create_chart_of_accounts_based_on = "Existing Company" company.existing_company = "_Test Company" company.save() - + expected_results = { "Debtors - CFEC": { "account_type": "Receivable", @@ -37,7 +37,7 @@ class TestCompany(unittest.TestCase): "parent_account": "Cash In Hand - CFEC" } } - + for account, acc_property in expected_results.items(): acc = frappe.get_doc("Account", account) for prop, val in acc_property.items(): @@ -50,14 +50,14 @@ class TestCompany(unittest.TestCase): countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France", "Guatemala", "Indonesia", "Italy", "Mexico", "Nicaragua", "Netherlands", "Singapore", "Brazil", "Argentina", "Hungary", "Taiwan"] - + for country in countries: templates = get_charts_for_country(country) if len(templates) != 1 and "Standard" in templates: templates.remove("Standard") - + self.assertTrue(templates) - + for template in templates: try: company = frappe.new_doc("Company") @@ -67,11 +67,11 @@ class TestCompany(unittest.TestCase): company.create_chart_of_accounts_based_on = "Standard Template" company.chart_of_accounts = template company.save() - - account_types = ["Cost of Goods Sold", "Depreciation", - "Expenses Included In Valuation", "Fixed Asset", "Payable", "Receivable", + + account_types = ["Cost of Goods Sold", "Depreciation", + "Expenses Included In Valuation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment", "Stock Received But Not Billed", "Bank", "Cash", "Stock"] - + for account_type in account_types: filters = { "company": template, diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index 58d8b5c334..21302417d2 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -62,5 +62,16 @@ "domain": "Manufacturing", "chart_of_accounts": "Standard", "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "TCP1", + "company_name": "_Test Company with perpetual inventory", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "enable_perpetual_inventory": 1, + "default_holiday_list": "_Test Holiday List" } ] diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index 32a03e7373..a4d4cbd8ef 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -21,7 +21,7 @@ def get_warehouse_account_map(company=None): filters['company'] = company for d in frappe.get_all('Warehouse', - fields = ["name", "account", "parent_warehouse", "company"], + fields = ["name", "account", "parent_warehouse", "company", "is_group"], filters = filters, order_by="lft, rgt"): if not d.account: diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 56b460781c..32445a618d 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -8,10 +8,13 @@ import unittest from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no from frappe.utils import cint - +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestBatch(unittest.TestCase): + def setUp(self): + set_perpetual_inventory(0) + def test_item_has_batch_enabled(self): self.assertRaises(ValidationError, frappe.get_doc({ "doctype": "Batch", diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 91b6f4c606..dc92c5c9ff 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -20,18 +20,11 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \ import create_stock_reconciliation, set_valuation_method from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account +from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse class TestDeliveryNote(unittest.TestCase): - def tearDown(self): - target_warehouse = "_Test Warehouse 1 - _TC" - company = "_Test Company" - if not frappe.db.exists("Account", target_warehouse): - parent_account = frappe.db.get_value('Account', - {'company': company, 'is_group':1, 'account_type': 'Stock'},'name') - - account = create_account(account_name="_Test Warehouse 1", \ - account_type="Stock", parent_account= parent_account, company=company) - frappe.db.set_value('Warehouse', target_warehouse, 'account', account) + def setUp(self): + set_perpetual_inventory(0) def test_over_billing_against_dn(self): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) @@ -68,17 +61,16 @@ class TestDeliveryNote(unittest.TestCase): self.assertFalse(get_gl_entries("Delivery Note", dn.name)) def test_delivery_note_gl_entry(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') set_valuation_method("_Test Item", "FIFO") - make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100) + make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) - stock_in_hand_account = get_inventory_account('_Test Company') + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') prev_bal = get_balance_on(stock_in_hand_account) - dn = create_delivery_note() + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") gl_entries = get_gl_entries("Delivery Note", dn.name) self.assertTrue(gl_entries) @@ -88,7 +80,7 @@ class TestDeliveryNote(unittest.TestCase): expected_values = { stock_in_hand_account: [0.0, stock_value_difference], - "Cost of Goods Sold - _TC": [stock_value_difference, 0.0] + "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0] } for i, gle in enumerate(gl_entries): self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) @@ -98,7 +90,7 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(bal, prev_bal - stock_value_difference) # back dated incoming entry - make_stock_entry(posting_date=add_days(nowdate(), -2), target="_Test Warehouse - _TC", + make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1", qty=5, basic_rate=100) gl_entries = get_gl_entries("Delivery Note", dn.name) @@ -109,27 +101,25 @@ class TestDeliveryNote(unittest.TestCase): expected_values = { stock_in_hand_account: [0.0, stock_value_difference], - "Cost of Goods Sold - _TC": [stock_value_difference, 0.0] + "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0] } for i, gle in enumerate(gl_entries): self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) dn.cancel() self.assertFalse(get_gl_entries("Delivery Note", dn.name)) - set_perpetual_inventory(0, company) def test_delivery_note_gl_entry_packing_item(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=100) + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=10, basic_rate=100) make_stock_entry(item_code="_Test Item Home Desktop 100", - target="_Test Warehouse - _TC", qty=10, basic_rate=100) + target="Stores - TCP1", qty=10, basic_rate=100) - stock_in_hand_account = get_inventory_account('_Test Company') + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') prev_bal = get_balance_on(stock_in_hand_account) - dn = create_delivery_note(item_code="_Test Product Bundle Item") + dn = create_delivery_note(item_code="_Test Product Bundle Item", company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") stock_value_diff_rm1 = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name, "item_code": "_Test Item"}, @@ -146,7 +136,7 @@ class TestDeliveryNote(unittest.TestCase): expected_values = { stock_in_hand_account: [0.0, stock_value_diff], - "Cost of Goods Sold - _TC": [stock_value_diff, 0.0] + "Cost of Goods Sold - TCP1": [stock_value_diff, 0.0] } for i, gle in enumerate(gl_entries): self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) @@ -158,8 +148,6 @@ class TestDeliveryNote(unittest.TestCase): dn.cancel() self.assertFalse(get_gl_entries("Delivery Note", dn.name)) - set_perpetual_inventory(0, company) - def test_serialized(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no)[0] @@ -218,16 +206,16 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(cstr(serial_no.get(field)), value) def test_sales_return_for_non_bundled_items(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) - actual_qty_0 = get_qty_after_transaction() + actual_qty_0 = get_qty_after_transaction(warehouse="Stores - TCP1") - dn = create_delivery_note(qty=5, rate=500) + dn = create_delivery_note(qty=5, rate=500, warehouse="Stores - TCP1", company=company, + expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") - actual_qty_1 = get_qty_after_transaction() + actual_qty_1 = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty_0 - 5, actual_qty_1) # outgoing_rate @@ -235,9 +223,10 @@ class TestDeliveryNote(unittest.TestCase): "voucher_no": dn.name}, "stock_value_difference") / 5 # return entry - dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500) + dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500, + company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") - actual_qty_2 = get_qty_after_transaction() + actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty_1 + 2, actual_qty_2) @@ -246,27 +235,29 @@ class TestDeliveryNote(unittest.TestCase): ["incoming_rate", "stock_value_difference"]) self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3))) - stock_in_hand_account = get_inventory_account('_Test Company', dn1.items[0].warehouse) + stock_in_hand_account = get_inventory_account(company, dn1.items[0].warehouse) gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "account": stock_in_hand_account}, "debit") self.assertEqual(gle_warehouse_amount, stock_value_difference) - set_perpetual_inventory(0, company) - def test_return_single_item_from_bundled_items(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - create_stock_reconciliation(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, rate=100) - create_stock_reconciliation(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", - qty=50, rate=100) + create_stock_reconciliation(item_code="_Test Item", + warehouse="Stores - TCP1", qty=50, rate=100, + company=company, expense_account = "Stock Adjustment - TCP1") + create_stock_reconciliation(item_code="_Test Item Home Desktop 100", + warehouse="Stores - TCP1", qty=50, rate=100, + company=company, expense_account = "Stock Adjustment - TCP1") - dn = create_delivery_note(item_code="_Test Product Bundle Item", qty=5, rate=500) + dn = create_delivery_note(item_code="_Test Product Bundle Item", qty=5, rate=500, + company=company, warehouse="Stores - TCP1", + expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") # Qty after delivery - actual_qty_1 = get_qty_after_transaction() + actual_qty_1 = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty_1, 25) # outgoing_rate @@ -274,10 +265,12 @@ class TestDeliveryNote(unittest.TestCase): "voucher_no": dn.name, "item_code": "_Test Item"}, "stock_value_difference") / 25 # return 'test item' from packed items - dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-10, rate=500) + dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-10, rate=500, + company=company, warehouse="Stores - TCP1", + expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") # qty after return - actual_qty_2 = get_qty_after_transaction() + actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty_2, 35) # Check incoming rate for return entry @@ -286,7 +279,7 @@ class TestDeliveryNote(unittest.TestCase): ["incoming_rate", "stock_value_difference"]) self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3))) - stock_in_hand_account = get_inventory_account('_Test Company', dn1.items[0].warehouse) + stock_in_hand_account = get_inventory_account(company, dn1.items[0].warehouse) # Check gl entry for warehouse gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note", @@ -294,33 +287,33 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(gle_warehouse_amount, stock_value_difference) - set_perpetual_inventory(0, company) def test_return_entire_bundled_items(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') create_stock_reconciliation(item_code="_Test Item", - target="_Test Warehouse - _TC", qty=50, rate=100) + warehouse="Stores - TCP1", qty=50, rate=100, + company=company, expense_account = "Stock Adjustment - TCP1") create_stock_reconciliation(item_code="_Test Item Home Desktop 100", - target="_Test Warehouse - _TC", qty=50, rate=100) + warehouse="Stores - TCP1", qty=50, rate=100, + company=company, expense_account = "Stock Adjustment - TCP1") - actual_qty = get_qty_after_transaction() + actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty, 50) dn = create_delivery_note(item_code="_Test Product Bundle Item", - qty=5, rate=500) + qty=5, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") # qty after return - actual_qty = get_qty_after_transaction() + actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty, 25) # return bundled item dn1 = create_delivery_note(item_code='_Test Product Bundle Item', is_return=1, - return_against=dn.name, qty=-2, rate=500) + return_against=dn.name, qty=-2, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") # qty after return - actual_qty = get_qty_after_transaction() + actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1") self.assertEqual(actual_qty, 35) # Check incoming rate for return entry @@ -337,8 +330,6 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(gle_warehouse_amount, 1400) - set_perpetual_inventory(0, company) - def test_return_for_serialized_items(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no)[0] @@ -375,56 +366,44 @@ class TestDeliveryNote(unittest.TestCase): }) def test_delivery_of_bundled_items_to_target_warehouse(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') set_valuation_method("_Test Item", "FIFO") set_valuation_method("_Test Item Home Desktop 100", "FIFO") - for warehouse in ("_Test Warehouse - _TC", "_Test Warehouse 1 - _TC"): - create_stock_reconciliation(item_code="_Test Item", target=warehouse, - qty=100, rate=100) - create_stock_reconciliation(item_code="_Test Item Home Desktop 100", - target=warehouse, qty=100, rate=100) + target_warehouse=get_warehouse(company=company, abbr="TCP1", + warehouse_name="_Test Customer Warehouse").name + + for warehouse in ("Stores - TCP1", target_warehouse): + create_stock_reconciliation(item_code="_Test Item", warehouse=warehouse, company = company, + expense_account = "Stock Adjustment - TCP1", qty=500, rate=100) + create_stock_reconciliation(item_code="_Test Item Home Desktop 100", company = company, + expense_account = "Stock Adjustment - TCP1", warehouse=warehouse, qty=500, rate=100) - opening_qty_test_warehouse_1 = get_qty_after_transaction(warehouse="_Test Warehouse 1 - _TC") dn = create_delivery_note(item_code="_Test Product Bundle Item", - qty=5, rate=500, target_warehouse="_Test Warehouse 1 - _TC", do_not_submit=True) + company='_Test Company with perpetual inventory', cost_center = 'Main - TCP1', + expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True, qty=5, rate=500, + warehouse="Stores - TCP1", target_warehouse=target_warehouse) dn.submit() # qty after delivery - actual_qty = get_qty_after_transaction(warehouse="_Test Warehouse - _TC") - self.assertEqual(actual_qty, 75) + actual_qty_at_source = get_qty_after_transaction(warehouse="Stores - TCP1") + self.assertEqual(actual_qty_at_source, 475) - actual_qty = get_qty_after_transaction(warehouse="_Test Warehouse 1 - _TC") - self.assertEqual(actual_qty, opening_qty_test_warehouse_1 + 25) + actual_qty_at_target = get_qty_after_transaction(warehouse=target_warehouse) + self.assertEqual(actual_qty_at_target, 525) - # stock value diff for source warehouse - # for "_Test Item" + # stock value diff for source warehouse for "_Test Item" stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name, - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, + "item_code": "_Test Item", "warehouse": "Stores - TCP1"}, "stock_value_difference") # stock value diff for target warehouse stock_value_difference1 = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name, - "item_code": "_Test Item", "warehouse": "_Test Warehouse 1 - _TC"}, - "stock_value_difference") - - self.assertEqual(abs(stock_value_difference), stock_value_difference1) - - # for "_Test Item Home Desktop 100" - stock_value_difference = frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Delivery Note", "voucher_no": dn.name, - "item_code": "_Test Item Home Desktop 100", "warehouse": "_Test Warehouse - _TC"}, - "stock_value_difference") - - # stock value diff for target warehouse - stock_value_difference1 = frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Delivery Note", "voucher_no": dn.name, - "item_code": "_Test Item Home Desktop 100", "warehouse": "_Test Warehouse 1 - _TC"}, + "item_code": "_Test Item", "warehouse": target_warehouse}, "stock_value_difference") self.assertEqual(abs(stock_value_difference), stock_value_difference1) @@ -435,21 +414,20 @@ class TestDeliveryNote(unittest.TestCase): stock_value_difference = abs(frappe.db.sql("""select sum(stock_value_difference) from `tabStock Ledger Entry` where voucher_type='Delivery Note' and voucher_no=%s - and warehouse='_Test Warehouse - _TC'""", dn.name)[0][0]) + and warehouse='Stores - TCP1'""", dn.name)[0][0]) expected_values = { - "Stock In Hand - _TC": [0.0, stock_value_difference], - "_Test Warehouse 1 - _TC": [stock_value_difference, 0.0] + "Stock In Hand - TCP1": [0.0, stock_value_difference], + target_warehouse: [stock_value_difference, 0.0] } for i, gle in enumerate(gl_entries): self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) - set_perpetual_inventory(0, company) - def test_closed_delivery_note(self): from erpnext.stock.doctype.delivery_note.delivery_note import update_delivery_note_status - dn = create_delivery_note(do_not_submit=True) + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True) + dn.submit() update_delivery_note_status(dn.name, "Closed") @@ -574,24 +552,23 @@ class TestDeliveryNote(unittest.TestCase): accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 accounts_settings.save() - cost_center = "_Test Cost Center for BS Account - _TC" - create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") + cost_center = "_Test Cost Center for BS Account - TCP1" + create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') set_valuation_method("_Test Item", "FIFO") - make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100) + make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) - stock_in_hand_account = get_inventory_account('_Test Company') - dn = create_delivery_note(cost_center=cost_center) + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', expense_account = "Cost of Goods Sold - TCP1", cost_center=cost_center) gl_entries = get_gl_entries("Delivery Note", dn.name) self.assertTrue(gl_entries) expected_values = { - "Cost of Goods Sold - _TC": { + "Cost of Goods Sold - TCP1": { "cost_center": cost_center }, stock_in_hand_account: { @@ -600,8 +577,6 @@ class TestDeliveryNote(unittest.TestCase): } for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - - set_perpetual_inventory(0, company) accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.save() @@ -609,23 +584,22 @@ class TestDeliveryNote(unittest.TestCase): accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.save() - cost_center = "_Test Cost Center - _TC" + cost_center = "Main - TCP1" - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') set_valuation_method("_Test Item", "FIFO") - make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100) + make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100) - stock_in_hand_account = get_inventory_account('_Test Company') - dn = create_delivery_note() + stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory') + dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1") gl_entries = get_gl_entries("Delivery Note", dn.name) self.assertTrue(gl_entries) expected_values = { - "Cost of Goods Sold - _TC": { + "Cost of Goods Sold - TCP1": { "cost_center": cost_center }, stock_in_hand_account: { @@ -635,8 +609,6 @@ class TestDeliveryNote(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - set_perpetual_inventory(0, company) - def test_make_sales_invoice_from_dn_for_returned_qty(self): from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice @@ -702,7 +674,7 @@ def create_delivery_note(**args): "rate": args.rate or 100, "conversion_factor": 1.0, "allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1, - "expense_account": "Cost of Goods Sold - _TC", + "expense_account": args.expense_account or "Cost of Goods Sold - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, "target_warehouse": args.target_warehouse diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index d5700fe514..f045e4f911 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -12,9 +12,11 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_rm_stock_entry import unittest +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestItemAlternative(unittest.TestCase): def setUp(self): + set_perpetual_inventory(0) make_items() def test_alternative_item_for_subcontract_rm(self): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index c32f028b08..4dc0b7b059 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -14,15 +14,15 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) - set_perpetual_inventory(1) - pr = frappe.copy_doc(pr_test_records[0]) - pr.submit() + + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True) + last_sle = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pr.doctype, "voucher_no": pr.name, "item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "Stores - TCP1" }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) @@ -35,7 +35,7 @@ class TestLandedCostVoucher(unittest.TestCase): "voucher_type": pr.doctype, "voucher_no": pr.name, "item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "Stores - TCP1" }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) @@ -48,55 +48,56 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertTrue(gl_entries) stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) - fixed_asset_account = get_inventory_account(pr.company, pr.get("items")[1].warehouse) + fixed_asset_account = get_inventory_account(pr.company, pr.get("items")[1].warehouse) if stock_in_hand_account == fixed_asset_account: expected_values = { stock_in_hand_account: [800.0, 0.0], - "Stock Received But Not Billed - _TC": [0.0, 500.0], - "Expenses Included In Valuation - _TC": [0.0, 300.0] + "Stock Received But Not Billed - TCP1": [0.0, 500.0], + "Expenses Included In Valuation - TCP1": [0.0, 300.0] } - + else: expected_values = { stock_in_hand_account: [400.0, 0.0], fixed_asset_account: [400.0, 0.0], - "Stock Received But Not Billed - _TC": [0.0, 500.0], - "Expenses Included In Valuation - _TC": [0.0, 300.0] + "Stock Received But Not Billed - TCP1": [0.0, 500.0], + "Expenses Included In Valuation - TCP1": [0.0, 300.0] } for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) - set_perpetual_inventory(0) - + def test_landed_cost_voucher_against_purchase_invoice(self): - set_perpetual_inventory(1) - + pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(), - posting_time=frappe.utils.nowtime()) + posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", + company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", + warehouse= "Stores - TCP1", cost_center = "Main - TCP1", + expense_account ="_Test Account Cost for Goods Sold - TCP1") last_sle = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pi.doctype, "voucher_no": pi.name, "item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "Stores - TCP1" }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) submit_landed_cost_voucher("Purchase Invoice", pi.name) - - pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, + + pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount") - + self.assertEqual(pi_lc_value, 50.0) last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", { "voucher_type": pi.doctype, "voucher_no": pi.name, "item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "Stores - TCP1" }, fieldname=["qty_after_transaction", "stock_value"], as_dict=1) @@ -111,21 +112,21 @@ class TestLandedCostVoucher(unittest.TestCase): expected_values = { stock_in_hand_account: [300.0, 0.0], - "Creditors - _TC": [0.0, 250.0], - "Expenses Included In Valuation - _TC": [0.0, 50.0] + "Creditors - TCP1": [0.0, 250.0], + "Expenses Included In Valuation - TCP1": [0.0, 50.0] } for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) - set_perpetual_inventory(0) - - def test_landed_cost_voucher_for_serialized_item(self): - set_perpetual_inventory(1) - frappe.db.sql("delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')") - pr = frappe.copy_doc(pr_test_records[0]) + def test_landed_cost_voucher_for_serialized_item(self): + frappe.db.sql("delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')") + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, + get_taxes_and_charges = True, do_not_submit = True) + pr.items[0].item_code = "_Test Serialized Item" pr.items[0].serial_no = "SN001\nSN002\nSN003\nSN004\nSN005" pr.submit() @@ -138,39 +139,36 @@ class TestLandedCostVoucher(unittest.TestCase): ["warehouse", "purchase_rate"], as_dict=1) self.assertEqual(serial_no.purchase_rate - serial_no_rate, 5.0) - self.assertEqual(serial_no.warehouse, "_Test Warehouse - _TC") + self.assertEqual(serial_no.warehouse, "Stores - TCP1") - set_perpetual_inventory(0) def test_landed_cost_voucher_for_odd_numbers (self): - set_perpetual_inventory(1) - pr = make_purchase_receipt(do_not_save=True) - pr.items[0].cost_center = "_Test Company - _TC" + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", do_not_save=True) + pr.items[0].cost_center = "Main - TCP1" for x in range(2): pr.append("items", { "item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC", - "cost_center": "_Test Company - _TC", + "warehouse": "Stores - TCP1", + "cost_center": "Main - TCP1", "qty": 5, "rate": 50 }) pr.submit() lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, 123.22) - + self.assertEqual(lcv.items[0].applicable_charges, 41.07) - self.assertEqual(lcv.items[2].applicable_charges, 41.08) - - set_perpetual_inventory(0) + self.assertEqual(lcv.items[2].applicable_charges, 41.08) + def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges=50): ref_doc = frappe.get_doc(receipt_document_type, receipt_document) - + lcv = frappe.new_doc("Landed Cost Voucher") lcv.company = "_Test Company" lcv.distribute_charges_based_on = 'Amount' - + lcv.set("purchase_receipts", [{ "receipt_document_type": receipt_document_type, "receipt_document": receipt_document, @@ -178,7 +176,7 @@ def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges= "posting_date": ref_doc.posting_date, "grand_total": ref_doc.base_grand_total }]) - + lcv.set("taxes", [{ "description": "Insurance Charges", "account": "_Test Account Insurance Charges - _TC", @@ -186,13 +184,13 @@ def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges= }]) lcv.insert() - + distribute_landed_cost_on_items(lcv) - + lcv.submit() return lcv - + def distribute_landed_cost_on_items(lcv): based_on = lcv.distribute_charges_based_on.lower() total = sum([flt(d.get(based_on)) for d in lcv.get("items")]) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index ab9311b480..e9ddf9d976 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -14,6 +14,7 @@ from erpnext.stock.doctype.item.test_item import make_item from six import iteritems class TestPurchaseReceipt(unittest.TestCase): def setUp(self): + set_perpetual_inventory(0) frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) def test_make_purchase_invoice(self): @@ -32,7 +33,6 @@ class TestPurchaseReceipt(unittest.TestCase): def test_purchase_receipt_no_gl_entry(self): company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(0, company) existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "stock_value") @@ -52,33 +52,29 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) def test_purchase_receipt_gl_entry(self): - pr = frappe.copy_doc(test_records[0]) - set_perpetual_inventory(1, pr.company) + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True) self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) - pr.insert() - pr.submit() gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertTrue(gl_entries) - stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) - fixed_asset_account = get_inventory_account(pr.company, pr.get("items")[1].warehouse) + stock_in_hand_account = get_inventory_account(pr.company, pr.items[0].warehouse) + fixed_asset_account = get_inventory_account(pr.company, pr.items[1].warehouse) if stock_in_hand_account == fixed_asset_account: expected_values = { stock_in_hand_account: [750.0, 0.0], - "Stock Received But Not Billed - _TC": [0.0, 500.0], - "Expenses Included In Valuation - _TC": [0.0, 250.0] + "Stock Received But Not Billed - TCP1": [0.0, 500.0], + "Expenses Included In Valuation - TCP1": [0.0, 250.0] } else: expected_values = { stock_in_hand_account: [375.0, 0.0], fixed_asset_account: [375.0, 0.0], - "Stock Received But Not Billed - _TC": [0.0, 500.0], - "Expenses Included In Valuation - _TC": [0.0, 250.0] + "Stock Received But Not Billed - TCP1": [0.0, 500.0], + "Expenses Included In Valuation - TCP1": [0.0, 250.0] } - for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) @@ -86,8 +82,6 @@ class TestPurchaseReceipt(unittest.TestCase): pr.cancel() self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) - set_perpetual_inventory(0, pr.company) - def test_subcontracting(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -132,11 +126,10 @@ class TestPurchaseReceipt(unittest.TestCase): pr.get("items")[0].rejected_warehouse) def test_purchase_return(self): - set_perpetual_inventory() - pr = make_purchase_receipt() + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") - return_pr = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-2) + return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2) # check sle outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", @@ -153,28 +146,28 @@ class TestPurchaseReceipt(unittest.TestCase): expected_values = { stock_in_hand_account: [0.0, 100.0], - "Stock Received But Not Billed - _TC": [100.0, 0.0], + "Stock Received But Not Billed - TCP1": [100.0, 0.0], } for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) - set_perpetual_inventory(0) def test_purchase_return_for_rejected_qty(self): - set_perpetual_inventory() + from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse - pr = make_purchase_receipt(received_qty=4, qty=2) + rejected_warehouse=get_warehouse(company = "_Test Company with perpetual inventory", abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name + print(rejected_warehouse) + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", received_qty=4, qty=2, rejected_warehouse=rejected_warehouse) - return_pr = make_purchase_receipt(is_return=1, return_against=pr.name, received_qty = -4, qty=-2) + return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, received_qty = -4, qty=-2, rejected_warehouse=rejected_warehouse) actual_qty = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", "voucher_no": return_pr.name, 'warehouse': return_pr.items[0].rejected_warehouse}, "actual_qty") self.assertEqual(actual_qty, -2) - set_perpetual_inventory(0) def test_purchase_return_for_serialized_items(self): def _check_serial_no_values(serial_no, field_values): @@ -337,7 +330,6 @@ class TestPurchaseReceipt(unittest.TestCase): pr.cancel() serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] self.assertEquals(len(serial_nos), 0) - #frappe.db.sql("delete from `tabLocation") frappe.db.sql("delete from `tabAsset`") def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self): @@ -345,8 +337,8 @@ class TestPurchaseReceipt(unittest.TestCase): accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 accounts_settings.save() - cost_center = "_Test Cost Center for BS Account - _TC" - create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") + cost_center = "_Test Cost Center for BS Account - TCP1" + create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory") if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ @@ -354,8 +346,7 @@ class TestPurchaseReceipt(unittest.TestCase): 'location_name': 'Test Location' }).insert() - set_perpetual_inventory(1, "_Test Company") - pr = make_purchase_receipt(cost_center=cost_center) + pr = make_purchase_receipt(cost_center=cost_center, company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) gl_entries = get_gl_entries("Purchase Receipt", pr.name) @@ -363,7 +354,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertTrue(gl_entries) expected_values = { - "Stock Received But Not Billed - _TC": { + "Stock Received But Not Billed - TCP1": { "cost_center": cost_center }, stock_in_hand_account: { @@ -373,7 +364,6 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - set_perpetual_inventory(0, pr.company) accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.save() @@ -387,9 +377,7 @@ class TestPurchaseReceipt(unittest.TestCase): 'doctype': 'Location', 'location_name': 'Test Location' }).insert() - - set_perpetual_inventory(1, "_Test Company") - pr = make_purchase_receipt() + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse) gl_entries = get_gl_entries("Purchase Receipt", pr.name) @@ -397,7 +385,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertTrue(gl_entries) expected_values = { - "Stock Received But Not Billed - _TC": { + "Stock Received But Not Billed - TCP1": { "cost_center": None }, stock_in_hand_account: { @@ -407,8 +395,6 @@ class TestPurchaseReceipt(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - set_perpetual_inventory(0, pr.company) - def test_make_purchase_invoice_from_pr_for_returned_qty(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po @@ -452,6 +438,78 @@ def get_gl_entries(voucher_type, voucher_no): from `tabGL Entry` where voucher_type=%s and voucher_no=%s order by account desc""", (voucher_type, voucher_no), as_dict=1) +def get_taxes(**args): + + args = frappe._dict(args) + + return [{'account_head': '_Test Account Shipping Charges - TCP1', + 'add_deduct_tax': 'Add', + 'category': 'Valuation and Total', + 'charge_type': 'Actual', + 'cost_center': args.cost_center or 'Main - TCP1', + 'description': 'Shipping Charges', + 'doctype': 'Purchase Taxes and Charges', + 'parentfield': 'taxes', + 'rate': 100.0, + 'tax_amount': 100.0}, + {'account_head': '_Test Account VAT - TCP1', + 'add_deduct_tax': 'Add', + 'category': 'Total', + 'charge_type': 'Actual', + 'cost_center': args.cost_center or 'Main - TCP1', + 'description': 'VAT', + 'doctype': 'Purchase Taxes and Charges', + 'parentfield': 'taxes', + 'rate': 120.0, + 'tax_amount': 120.0}, + {'account_head': '_Test Account Customs Duty - TCP1', + 'add_deduct_tax': 'Add', + 'category': 'Valuation', + 'charge_type': 'Actual', + 'cost_center': args.cost_center or 'Main - TCP1', + 'description': 'Customs Duty', + 'doctype': 'Purchase Taxes and Charges', + 'parentfield': 'taxes', + 'rate': 150.0, + 'tax_amount': 150.0}] + +def get_items(**args): + args = frappe._dict(args) + return [{ + "base_amount": 250.0, + "conversion_factor": 1.0, + "description": "_Test Item", + "doctype": "Purchase Receipt Item", + "item_code": "_Test Item", + "item_name": "_Test Item", + "parentfield": "items", + "qty": 5.0, + "rate": 50.0, + "received_qty": 5.0, + "rejected_qty": 0.0, + "stock_uom": "_Test UOM", + "uom": "_Test UOM", + "warehouse": args.warehouse or "_Test Warehouse - _TC", + "cost_center": args.cost_center or "Main - _TC" + }, + { + "base_amount": 250.0, + "conversion_factor": 1.0, + "description": "_Test Item Home Desktop 100", + "doctype": "Purchase Receipt Item", + "item_code": "_Test Item Home Desktop 100", + "item_name": "_Test Item Home Desktop 100", + "parentfield": "items", + "qty": 5.0, + "rate": 50.0, + "received_qty": 5.0, + "rejected_qty": 0.0, + "stock_uom": "_Test UOM", + "uom": "_Test UOM", + "warehouse": args.warehouse or "_Test Warehouse 1 - _TC", + "cost_center": args.cost_center or "Main - _TC" + }] + def make_purchase_receipt(**args): if not frappe.db.exists('Location', 'Test Location'): frappe.get_doc({ @@ -468,7 +526,7 @@ def make_purchase_receipt(**args): pr.company = args.company or "_Test Company" pr.supplier = args.supplier or "_Test Supplier" pr.is_subcontracted = args.is_subcontracted or "No" - pr.supplier_warehouse = "_Test Warehouse 1 - _TC" + pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pr.currency = args.currency or "INR" pr.is_return = args.is_return pr.return_against = args.return_against @@ -492,6 +550,16 @@ def make_purchase_receipt(**args): "asset_location": args.location or "Test Location" }) + if args.get_multiple_items: + pr.items = [] + for item in get_items(warehouse= args.warehouse, cost_center = args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center')): + pr.append("items", item) + + + if args.get_taxes_and_charges: + for tax in get_taxes(): + pr.append("taxes", tax) + if not args.do_not_save: pr.insert() if not args.do_not_submit: diff --git a/erpnext/stock/doctype/purchase_receipt/test_records.json b/erpnext/stock/doctype/purchase_receipt/test_records.json index 7c20991a1b..e7ea9af6b9 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_records.json +++ b/erpnext/stock/doctype/purchase_receipt/test_records.json @@ -83,39 +83,5 @@ } ], "supplier": "_Test Supplier" - }, - - - { - "buying_price_list": "_Test Price List", - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "doctype": "Purchase Receipt", - "base_grand_total": 5000.0, - "is_subcontracted": "Yes", - "base_net_total": 5000.0, - "posting_date": "2013-02-12", - "items": [ - { - "base_amount": 5000.0, - "conversion_factor": 1.0, - "description": "_Test FG Item", - "doctype": "Purchase Receipt Item", - "item_code": "_Test FG Item", - "item_name": "_Test FG Item", - "parentfield": "items", - "qty": 10.0, - "rate": 500.0, - "received_qty": 10.0, - "rejected_qty": 0.0, - "stock_uom": "_Test UOM", - "uom": "_Test UOM", - "warehouse": "_Test Warehouse - _TC", - "cost_center": "Main - _TC" - } - ], - "supplier": "_Test Supplier", - "supplier_warehouse": "_Test Warehouse - _TC" } ] \ No newline at end of file diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index ed70790b2c..ab061076e5 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -12,6 +12,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory test_dependencies = ["Item"] test_records = frappe.get_test_records('Serial No') @@ -37,6 +38,8 @@ class TestSerialNo(unittest.TestCase): self.assertTrue(SerialNoCannotCannotChangeError, sr.save) def test_inter_company_transfer(self): + set_perpetual_inventory(0, "_Test Company 1") + set_perpetual_inventory(0) se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos(se.get("items")[0].serial_no) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 3fa815dc82..941472bbf9 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -16,7 +16,6 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError - from six import iteritems def get_sle(**args): @@ -132,20 +131,19 @@ class TestStockEntry(unittest.TestCase): self.assertTrue(item_code in items) def test_material_receipt_gl_entry(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", - qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC") + mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company= company, + qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1") stock_in_hand_account = get_inventory_account(mr.company, mr.get("items")[0].t_warehouse) self.check_stock_ledger_entries("Stock Entry", mr.name, - [["_Test Item", "_Test Warehouse - _TC", 50.0]]) + [["_Test Item", "Stores - TCP1", 50.0]]) self.check_gl_entries("Stock Entry", mr.name, sorted([ [stock_in_hand_account, 5000.0, 0.0], - ["Stock Adjustment - _TC", 0.0, 5000.0] + ["Stock Adjustment - TCP1", 0.0, 5000.0] ]) ) @@ -158,29 +156,26 @@ class TestStockEntry(unittest.TestCase): where voucher_type='Stock Entry' and voucher_no=%s""", mr.name)) def test_material_issue_gl_entry(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company= company, + qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1") - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", - qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC") - - mi = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC", - qty=40, expense_account="Stock Adjustment - _TC") + mi = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", company=company, + qty=40, expense_account="Stock Adjustment - TCP1") self.check_stock_ledger_entries("Stock Entry", mi.name, - [["_Test Item", "_Test Warehouse - _TC", -40.0]]) + [["_Test Item", "Stores - TCP1", -40.0]]) - stock_in_hand_account = get_inventory_account(mi.company, "_Test Warehouse - _TC") + stock_in_hand_account = get_inventory_account(mi.company, "Stores - TCP1") stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry", "voucher_no": mi.name}, "stock_value_difference")) self.check_gl_entries("Stock Entry", mi.name, sorted([ [stock_in_hand_account, 0.0, stock_value_diff], - ["Stock Adjustment - _TC", stock_value_diff, 0.0] + ["Stock Adjustment - TCP1", stock_value_diff, 0.0] ]) ) - mi.cancel() self.assertFalse(frappe.db.sql("""select name from `tabStock Ledger Entry` @@ -190,16 +185,15 @@ class TestStockEntry(unittest.TestCase): where voucher_type='Stock Entry' and voucher_no=%s""", mi.name)) def test_material_transfer_gl_entry(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') create_stock_reconciliation(qty=100, rate=100) - mtn = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC", - target="_Test Warehouse 1 - _TC", qty=45) + mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", + target="Finished Goods - TCP1", qty=45) self.check_stock_ledger_entries("Stock Entry", mtn.name, - [["_Test Item", "_Test Warehouse - _TC", -45.0], ["_Test Item", "_Test Warehouse 1 - _TC", 45.0]]) + [["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]]) stock_in_hand_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse) @@ -212,7 +206,7 @@ class TestStockEntry(unittest.TestCase): else: stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry", - "voucher_no": mtn.name, "warehouse": "_Test Warehouse - _TC"}, "stock_value_difference")) + "voucher_no": mtn.name, "warehouse": "Stores - TCP1"}, "stock_value_difference")) self.check_gl_entries("Stock Entry", mtn.name, sorted([ @@ -255,14 +249,21 @@ class TestStockEntry(unittest.TestCase): set_perpetual_inventory(0, repack.company) def test_repack_with_additional_costs(self): - company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') - set_perpetual_inventory(1, company) + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) - repack = frappe.copy_doc(test_records[3]) + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company= company, + qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1") + + + repack = make_stock_entry(company = company, purpose="Repack", do_not_save=True) repack.posting_date = nowdate() repack.posting_time = nowtime() + items = get_multiple_items() + repack.items = [] + for item in items: + repack.append("items", item) + repack.set("additional_costs", [ { "description": "Actual Oerating Cost", @@ -292,13 +293,12 @@ class TestStockEntry(unittest.TestCase): self.check_gl_entries("Stock Entry", repack.name, sorted([ [stock_in_hand_account, 1200, 0.0], - ["Expenses Included In Valuation - _TC", 0.0, 1200.0] + ["Expenses Included In Valuation - TCP1", 0.0, 1200.0] ]) ) - set_perpetual_inventory(0, repack.company) def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle): - expected_sle.sort(key=lambda x: x[0]) + expected_sle.sort(key=lambda x: x[1]) # check stock ledger entries sle = frappe.db.sql("""select item_code, warehouse, actual_qty @@ -306,7 +306,7 @@ class TestStockEntry(unittest.TestCase): and voucher_no = %s order by item_code, warehouse, actual_qty""", (voucher_type, voucher_no), as_list=1) self.assertTrue(sle) - sle.sort(key=lambda x: x[0]) + sle.sort(key=lambda x: x[1]) for i, sle in enumerate(sle): self.assertEqual(expected_sle[i][0], sle[0]) @@ -773,14 +773,12 @@ class TestStockEntry(unittest.TestCase): self.assertEqual(doc.per_transferred, 100) def test_gle_for_opening_stock_entry(self): - set_perpetual_inventory(1) - - mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", - qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC", is_opening="Yes", do_not_save=True) + mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True) self.assertRaises(OpeningEntryAccountError, mr.save) - mr.items[0].expense_account = "Temporary Opening - _TC" + mr.items[0].expense_account = "Temporary Opening - TCP1" + mr.save() mr.submit() @@ -805,14 +803,42 @@ def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): def get_qty_after_transaction(**args): args = frappe._dict(args) - last_sle = get_previous_sle({ "item_code": args.item_code or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", "posting_date": args.posting_date or nowdate(), "posting_time": args.posting_time or nowtime() }) - return flt(last_sle.get("qty_after_transaction")) +def get_multiple_items(): + return [ + { + "conversion_factor": 1.0, + "cost_center": "Main - TCP1", + "doctype": "Stock Entry Detail", + "expense_account": "Stock Adjustment - TCP1", + "basic_rate": 100, + "item_code": "_Test Item", + "qty": 50.0, + "s_warehouse": "Stores - TCP1", + "stock_uom": "_Test UOM", + "transfer_qty": 50.0, + "uom": "_Test UOM" + }, + { + "conversion_factor": 1.0, + "cost_center": "Main - TCP1", + "doctype": "Stock Entry Detail", + "expense_account": "Stock Adjustment - TCP1", + "basic_rate": 5000, + "item_code": "_Test Item Home Desktop 100", + "qty": 1, + "stock_uom": "_Test UOM", + "t_warehouse": "Stores - TCP1", + "transfer_qty": 1, + "uom": "_Test UOM" + } + ] + test_records = frappe.get_test_records('Stock Entry') diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index cd05929743..e6d7e3fea7 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe, unittest from frappe.utils import flt, nowdate, nowtime -from erpnext.accounts.utils import get_stock_and_account_difference +from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items @@ -21,7 +21,6 @@ class TestStockReconciliation(unittest.TestCase): def setUpClass(self): create_batch_or_serial_no_items() frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) - insert_existing_sle() def test_reco_for_fifo(self): self._test_reco_sle_gle("FIFO") @@ -30,7 +29,8 @@ class TestStockReconciliation(unittest.TestCase): self._test_reco_sle_gle("Moving Average") def _test_reco_sle_gle(self, valuation_method): - set_perpetual_inventory() + insert_existing_sle(warehouse='Stores - TCP1') + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] input_data = [ @@ -46,14 +46,15 @@ class TestStockReconciliation(unittest.TestCase): last_sle = get_previous_sle({ "item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC", + "warehouse": "Stores - TCP1", "posting_date": d[2], "posting_time": d[3] }) # submit stock reconciliation stock_reco = create_stock_reconciliation(qty=d[0], rate=d[1], - posting_date=d[2], posting_time=d[3]) + posting_date=d[2], posting_time=d[3], warehouse="Stores - TCP1", + company=company, expense_account = "Stock Adjustment - TCP1") # check stock value sle = frappe.db.sql("""select * from `tabStock Ledger Entry` @@ -73,17 +74,18 @@ class TestStockReconciliation(unittest.TestCase): # no gl entries self.assertTrue(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) - self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) - stock_reco.cancel() + acc_bal, stock_bal, wh_list = get_stock_and_account_balance("Stock In Hand - TCP1", + stock_reco.posting_date, stock_reco.company) + self.assertEqual(acc_bal, stock_bal) - self.assertFalse(frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) + stock_reco.cancel() - self.assertFalse(frappe.db.get_value("GL Entry", - {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) + self.assertFalse(frappe.db.get_value("Stock Ledger Entry", + {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) - set_perpetual_inventory(0) + self.assertFalse(frappe.db.get_value("GL Entry", + {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name})) def test_get_items(self): create_warehouse("_Test Warehouse Group 1", {"is_group": 1}) @@ -203,17 +205,17 @@ class TestStockReconciliation(unittest.TestCase): stock_doc.cancel() -def insert_existing_sle(): +def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item", - target="_Test Warehouse - _TC", qty=10, basic_rate=700) + target=warehouse, qty=10, basic_rate=700) make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item", - source="_Test Warehouse - _TC", qty=15) + source=warehouse, qty=15) make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item", - target="_Test Warehouse - _TC", qty=15, basic_rate=1200) + target=warehouse, qty=15, basic_rate=1200) def create_batch_or_serial_no_items(): create_warehouse("_Test Warehouse for Stock Reco1", @@ -244,7 +246,10 @@ def create_stock_reconciliation(**args): sr.company = args.company or "_Test Company" sr.expense_account = args.expense_account or \ ("Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC") - sr.cost_center = args.cost_center or "_Test Cost Center - _TC" + sr.cost_center = args.cost_center \ + or frappe.get_cached_value("Company", sr.company, "cost_center") \ + or "_Test Cost Center - _TC" + sr.append("items", { "item_code": args.item_code or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", diff --git a/erpnext/stock/doctype/warehouse/test_records.json b/erpnext/stock/doctype/warehouse/test_records.json index 014cf3e501..e128558ed3 100644 --- a/erpnext/stock/doctype/warehouse/test_records.json +++ b/erpnext/stock/doctype/warehouse/test_records.json @@ -1,42 +1,36 @@ [ { "company": "_Test Company", - "create_account_under": "Stock Assets - _TC", "doctype": "Warehouse", "warehouse_name": "_Test Warehouse", "is_group": 0 }, { "company": "_Test Company", - "create_account_under": "Stock Assets - _TC", "doctype": "Warehouse", "warehouse_name": "_Test Scrap Warehouse", "is_group": 0 }, { "company": "_Test Company", - "create_account_under": "Fixed Assets - _TC", "doctype": "Warehouse", "warehouse_name": "_Test Warehouse 1", "is_group": 0 }, { "company": "_Test Company", - "create_account_under": "Fixed Assets - _TC", "doctype": "Warehouse", "warehouse_name": "_Test Warehouse 2", "is_group": 0 }, { "company": "_Test Company", - "create_account_under": "Stock Assets - _TC", "doctype": "Warehouse", "warehouse_name": "_Test Rejected Warehouse", "is_group": 0 }, { "company": "_Test Company 1", - "create_account_under": "Stock Assets - _TC1", "doctype": "Warehouse", "warehouse_name": "_Test Warehouse 2", "is_group": 0 diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index dc39e101ce..121222d7bc 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -101,8 +101,7 @@ def create_warehouse(warehouse_name, properties=None, company=None): w.warehouse_name = warehouse_name w.parent_warehouse = "_Test Warehouse Group - _TC" w.company = company - make_account_for_warehouse(warehouse_name, w) - w.account = warehouse_id + w.account = get_warehouse_account(warehouse_name, company) if properties: w.update(properties) w.save() @@ -110,9 +109,40 @@ def create_warehouse(warehouse_name, properties=None, company=None): else: return warehouse_id -def make_account_for_warehouse(warehouse_name, warehouse_obj): - if not frappe.db.exists("Account", warehouse_name + " - _TC"): - parent_account = frappe.db.get_value('Account', - {'company': warehouse_obj.company, 'is_group':1, 'account_type': 'Stock'},'name') - account = create_account(account_name=warehouse_name, \ - account_type="Stock", parent_account= parent_account, company=warehouse_obj.company) \ No newline at end of file +def get_warehouse(**args): + args = frappe._dict(args) + if(frappe.db.exists("Warehouse", args.warehouse_name + " - " + args.abbr)): + return frappe.get_doc("Warehouse", args.warehouse_name + " - " + args.abbr) + else: + w = frappe.get_doc({ + "company": args.company or "_Test Company", + "doctype": "Warehouse", + "warehouse_name": args.warehouse_name, + "is_group": 0, + "account": get_warehouse_account(args.warehouse_name, args.company, args.abbr) + }) + w.insert() + return w + +def get_warehouse_account(warehouse_name, company, company_abbr=None): + if not company_abbr: + company_abbr = frappe.get_cached_value("Company", company, 'abbr') + + if not frappe.db.exists("Account", warehouse_name + " - " + company_abbr): + return create_account( + account_name=warehouse_name, + parent_account=get_group_stock_account(company, company_abbr), + account_type='Stock', + company=company) + else: + return warehouse_name + " - " + company_abbr + + +def get_group_stock_account(company, company_abbr=None): + group_stock_account = frappe.db.get_value("Account", + filters={'account_type': 'Stock', 'is_group': 1, 'company': company}, fieldname='name') + if not group_stock_account: + if not company_abbr: + company_abbr = frappe.get_cached_value("Company", company, 'abbr') + group_stock_account = "Current Assets - " + company_abbr + return group_stock_account \ No newline at end of file From 81c217584a8d30361957abdda815118c5c00f71b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 31 Oct 2019 15:56:10 +0530 Subject: [PATCH 042/131] fix: purchase order issue, margin_rate_or_amount not there in the purchase documents (#19466) --- erpnext/controllers/accounts_controller.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 320a618f68..67f453d2b3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1172,6 +1172,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): data = json.loads(trans_items) + sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] parent = frappe.get_doc(parent_doctype, parent_doctype_name) for d in data: @@ -1204,18 +1205,22 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil # if rate is greater than price_list_rate, set margin # or set discount child_item.discount_percentage = 0 - child_item.margin_type = "Amount" - child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate, - child_item.precision("margin_rate_or_amount")) - child_item.rate_with_margin = child_item.rate + + if parent_doctype in sales_doctypes: + child_item.margin_type = "Amount" + child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate, + child_item.precision("margin_rate_or_amount")) + child_item.rate_with_margin = child_item.rate else: child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, child_item.precision("discount_percentage")) child_item.discount_amount = flt( child_item.price_list_rate) - flt(child_item.rate) - child_item.margin_type = "" - child_item.margin_rate_or_amount = 0 - child_item.rate_with_margin = 0 + + if parent_doctype in sales_doctypes: + child_item.margin_type = "" + child_item.margin_rate_or_amount = 0 + child_item.rate_with_margin = 0 child_item.flags.ignore_validate_update_after_submit = True if new_child_flag: From 4a1d000c336e640c46512134cd364e733d65e4e6 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 31 Oct 2019 15:57:15 +0530 Subject: [PATCH 043/131] fix: Test Case --- .../leave_application_dashboard.py | 6 ++++++ .../test_leave_application.py | 19 +++---------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py index 8075b7b5c5..c1d6a6665b 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py @@ -5,6 +5,12 @@ from frappe import _ def get_data(): return { + 'fieldname': 'leave_application', + 'transactions': [ + { + 'items': ['Attendance'] + } + ], 'reports': [ { 'label': _('Reports'), diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index ad141a5748..38ae808f27 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -72,7 +72,7 @@ class TestLeaveApplication(unittest.TestCase): application.to_date = "2013-01-05" return application - def test_attendance_creation(self): + def test_overwrite_attendance(self): '''check attendance is automatically created on leave approval''' make_allocation_record() application = self.get_application(_test_records[0]) @@ -82,7 +82,8 @@ class TestLeaveApplication(unittest.TestCase): application.insert() application.submit() - attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'], dict(leave_application = application.name)) + attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'], + dict(attendance_date=('between', ['2018-01-01', '2018-01-03']), docstatus=("!=", 2))) # attendance created for all 3 days self.assertEqual(len(attendance), 3) @@ -95,20 +96,6 @@ class TestLeaveApplication(unittest.TestCase): for d in ('2018-01-01', '2018-01-02', '2018-01-03'): self.assertTrue(getdate(d) in dates) - def test_overwrite_attendance(self): - # employee marked as absent - doc = frappe.new_doc("Attendance") - doc.employee = '_T-Employee-00001' - doc.attendance_date = '2018-01-01' - doc.company = '_Test Company' - doc.status = 'Absent' - doc.flags.ignore_validate = True - doc.insert(ignore_permissions=True) - doc.submit() - - # now check if the status has been updated - self.test_attendance_creation() - def test_block_list(self): self._clear_roles() From 77e4cf89f8bab4a4b3740007c28c154f8dff46ad Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 31 Oct 2019 15:57:31 +0530 Subject: [PATCH 044/131] fix: Pass parent_acc_name (#19450) --- erpnext/accounts/doctype/account/account.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 7cca8d2003..cccced8e0b 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -117,7 +117,7 @@ class Account(NestedSet): if not parent_acc_name_map: return - self.create_account_for_child_company(parent_acc_name_map, descendants) + self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) def validate_group_or_ledger(self): if self.get("__islocal"): @@ -159,7 +159,7 @@ class Account(NestedSet): if frappe.db.get_value("GL Entry", {"account": self.name}): frappe.throw(_("Currency can not be changed after making entries using some other currency")) - def create_account_for_child_company(self, parent_acc_name_map, descendants): + def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): for company in descendants: if not parent_acc_name_map.get(company): frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA") From 519ca54f539df6b648ed369a19ed91a6317dab0e Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 31 Oct 2019 16:00:04 +0530 Subject: [PATCH 045/131] feat: Show timesheets related to customer's projects on customer portal (#19443) * fix: Show timesheets related to customer's projects on customer portal * style: fix codacy --- .../projects/doctype/timesheet/timesheet.py | 34 ++++++++++++++----- .../includes/timesheet/timesheet_row.html | 23 +++++++------ 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 9ee292796c..bc88250c8a 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -353,17 +353,35 @@ def get_events(start, end, filters=None): def get_timesheets_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"): user = frappe.session.user # find customer name from contact. - customer = frappe.db.sql('''SELECT dl.link_name FROM `tabContact` AS c inner join \ - `tabDynamic Link` AS dl ON c.first_name=dl.link_name WHERE c.email_id=%s''',user) + customer = '' + timesheets = [] + + contact = frappe.db.exists('Contact', {'user': user}) + if contact: + # find customer + contact = frappe.get_doc('Contact', contact) + customer = contact.get_link_for('Customer') if customer: - # find list of Sales Invoice for made for customer. - sales_invoice = frappe.db.sql('''SELECT name FROM `tabSales Invoice` WHERE customer = %s''',customer) + sales_invoices = [d.name for d in frappe.get_all('Sales Invoice', filters={'customer': customer})] or [None] + projects = [d.name for d in frappe.get_all('Project', filters={'customer': customer})] # Return timesheet related data to web portal. - return frappe. db.sql('''SELECT ts.name, tsd.activity_type, ts.status, ts.total_billable_hours, \ - tsd.sales_invoice, tsd.project FROM `tabTimesheet` AS ts inner join `tabTimesheet Detail` \ - AS tsd ON tsd.parent = ts.name where tsd.sales_invoice IN %s order by\ - end_date asc limit {0} , {1}'''.format(limit_start, limit_page_length), [sales_invoice], as_dict = True) + timesheets = frappe.db.sql(''' + SELECT + ts.name, tsd.activity_type, ts.status, ts.total_billable_hours, + COALESCE(ts.sales_invoice, tsd.sales_invoice) AS sales_invoice, tsd.project + FROM `tabTimesheet` ts, `tabTimesheet Detail` tsd + WHERE tsd.parent = ts.name AND + ( + ts.sales_invoice IN %(sales_invoices)s OR + tsd.sales_invoice IN %(sales_invoices)s OR + tsd.project IN %(projects)s + ) + ORDER BY `end_date` ASC + LIMIT {0}, {1} + '''.format(limit_start, limit_page_length), dict(sales_invoices=sales_invoices, projects=projects), as_dict=True) #nosec + + return timesheets def get_list_context(context=None): return { diff --git a/erpnext/templates/includes/timesheet/timesheet_row.html b/erpnext/templates/includes/timesheet/timesheet_row.html index e9cfcda812..4852f59b5d 100644 --- a/erpnext/templates/includes/timesheet/timesheet_row.html +++ b/erpnext/templates/includes/timesheet/timesheet_row.html @@ -1,13 +1,14 @@ -
    - -
    -
    - {{ doc.name }} -
    -
    Billable Hours: {{ doc.total_billable_hours}}
    -
    {{ _(doc.sales_invoice) }}
    -
    {{ _(doc.project) }}
    -
    {{ _(doc.activity_type) }}
    +
    +
    +
    + + {{ doc.name }} +
    -
    +
    {{ doc.total_billable_hours }}
    +
    {{ doc.project or '' }}
    +
    {{ doc.sales_invoice or '' }}
    +
    {{ _(doc.activity_type) }}
    +
    +
    From 45c18b318472b043681cc845534452ded7474a0b Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 31 Oct 2019 16:00:52 +0530 Subject: [PATCH 046/131] fix: fetch employee department (#19433) --- .../employee_leave_balance_summary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py index 15a5da00f8..777de02238 100644 --- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py @@ -75,7 +75,7 @@ def get_data(filters): leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver) - if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \ + if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \ or ("HR Manager" in frappe.get_roles(user)): row = frappe._dict({ 'employee': employee.name, @@ -111,10 +111,10 @@ def get_conditions(filters): def get_department_leave_approver_map(department=None): conditions='' if department: - conditions='and department_name = %(department)s or parent_department = %(department)s'%{'department': department} + conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department} # get current department and all its child - department_list = frappe.db.sql_list(''' SELECT name FROM `tabDepartment` WHERE disabled=0 {0}'''.format(conditions)) #nosec + department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec # retrieve approvers list from current department and from its subsequent child departments approver_list = frappe.get_all('Department Approver', filters={ From 28710cdf99b453e3e3f5115b44551bfa5b6c6083 Mon Sep 17 00:00:00 2001 From: Ashish Shah Date: Thu, 31 Oct 2019 16:09:06 +0530 Subject: [PATCH 047/131] fix: coupon code changes suggested by prasad (#19352) * changes suggested by prasad * codacy correction --- erpnext/accounts/doctype/coupon_code/coupon_code.js | 9 +++++++++ erpnext/accounts/doctype/coupon_code/coupon_code.json | 10 ++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.js b/erpnext/accounts/doctype/coupon_code/coupon_code.js index 0bf097f8d5..da3a9f8132 100644 --- a/erpnext/accounts/doctype/coupon_code/coupon_code.js +++ b/erpnext/accounts/doctype/coupon_code/coupon_code.js @@ -2,6 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on('Coupon Code', { + setup: function(frm) { + frm.set_query("pricing_rule", function() { + return { + filters: [ + ["Pricing Rule","coupon_code_based", "=", "1"] + ] + }; + }); + }, coupon_name:function(frm){ if (frm.doc.__islocal===1) { frm.trigger("make_coupon_code"); diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.json b/erpnext/accounts/doctype/coupon_code/coupon_code.json index fafc63531f..7dc5e9dc78 100644 --- a/erpnext/accounts/doctype/coupon_code/coupon_code.json +++ b/erpnext/accounts/doctype/coupon_code/coupon_code.json @@ -24,6 +24,7 @@ ], "fields": [ { + "description": "e.g. \"Summer Holiday 2019 Offer 20\"", "fieldname": "coupon_name", "fieldtype": "Data", "label": "Coupon Name", @@ -50,7 +51,7 @@ "fieldtype": "Column Break" }, { - "description": "To be used to get discount", + "description": "unique e.g. SAVE20 To be used to get discount", "fieldname": "coupon_code", "fieldtype": "Data", "label": "Coupon Code", @@ -62,12 +63,13 @@ "fieldname": "pricing_rule", "fieldtype": "Link", "label": "Pricing Rule", - "options": "Pricing Rule" + "options": "Pricing Rule", + "reqd": 1 }, { "fieldname": "uses", "fieldtype": "Section Break", - "label": "Uses" + "label": "Validity and Usage" }, { "fieldname": "valid_from", @@ -113,7 +115,7 @@ "read_only": 1 } ], - "modified": "2019-10-15 14:12:22.686986", + "modified": "2019-10-19 14:48:14.602481", "modified_by": "Administrator", "module": "Accounts", "name": "Coupon Code", From c82aed071857ca2f20074704a24b0cd1801ecfa6 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 1 Nov 2019 12:36:33 +0530 Subject: [PATCH 048/131] fix: Add missing semicolon --- erpnext/stock/doctype/serial_no/serial_no_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no_list.js b/erpnext/stock/doctype/serial_no/serial_no_list.js index 559a9a7b8a..5b1e312f68 100644 --- a/erpnext/stock/doctype/serial_no/serial_no_list.js +++ b/erpnext/stock/doctype/serial_no/serial_no_list.js @@ -6,9 +6,9 @@ frappe.listview_settings['Serial No'] = { } else if (doc.delivery_document_type) { return [__("Delivered"), "green", "delivery_document_type,is,set|is_cancelled,=,No"]; } else if (doc.warranty_expiry_date && frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0) { - return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set|is_cancelled,=,No"] + return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set|is_cancelled,=,No"]; } else { return [__("Active"), "green", "delivery_document_type,is,not set|is_cancelled,=,No"]; - }; + } } }; From 1bb5d0119227e69d688cc7b74dee99246f6ed760 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 1 Nov 2019 14:24:21 +0530 Subject: [PATCH 049/131] fix: Wrong warehouse fetched in production plan --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 4dc98e7ade..10d9a474e0 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -622,7 +622,7 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): for data in po_items: planned_qty = data.get('required_qty') or data.get('planned_qty') ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty - warehouse = data.get("warehouse") or warehouse + warehouse = warehouse or data.get("warehouse") item_details = {} if data.get("bom") or data.get("bom_no"): From 7091a2102c6839ed0246f54cfa121813905383ca Mon Sep 17 00:00:00 2001 From: Diksha Date: Mon, 4 Nov 2019 12:03:35 +0530 Subject: [PATCH 050/131] fix(sales order): rename delivery to delivery note on sales order make button --- erpnext/selling/doctype/sales_order/sales_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index a2b85446be..85e81436d1 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -150,7 +150,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( // delivery note if(flt(doc.per_delivered, 6) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) { - this.frm.add_custom_button(__('Delivery'), () => this.make_delivery_note_based_on_delivery_date(), __('Create')); + this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create')); this.frm.add_custom_button(__('Work Order'), () => this.make_work_order(), __('Create')); } From 7e2b030052ca2e0ea9766500749d666b24bdaa36 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 4 Nov 2019 13:06:50 +0530 Subject: [PATCH 051/131] fix: fix dashboard with date range --- .../account_balance_timeline/account_balance_timeline.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index d098d8421c..716bef381b 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -19,6 +19,11 @@ def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_d else: chart = frappe._dict(frappe.parse_json(chart)) timespan = chart.timespan + + if chart.timespan == 'Select Date Range': + from_date = chart.from_date + to_date = chart.to_date + timegrain = chart.time_interval filters = frappe.parse_json(chart.filters_json) From 05710804cff18e57a9bcd43797aa31bf1883887f Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 4 Nov 2019 14:45:11 +0530 Subject: [PATCH 052/131] fix: invoice creation tool of invoices with no paid and outstanding amounts --- .../opening_invoice_creation_tool.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index ce8aba75b2..54464e71c4 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -32,8 +32,10 @@ class OpeningInvoiceCreationTool(Document): }) invoices_summary.update({company: _summary}) - paid_amount.append(invoice.paid_amount) - outstanding_amount.append(invoice.outstanding_amount) + if invoice.paid_amount: + paid_amount.append(invoice.paid_amount) + if invoice.outstanding_amount: + outstanding_amount.append(invoice.outstanding_amount) if paid_amount or outstanding_amount: max_count.update({ From 730c8a145d782929af994d71a2c188f47628e626 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 5 Nov 2019 12:46:52 +0530 Subject: [PATCH 053/131] minor: code cleanup --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8b42b19921..04e8a83e7f 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -207,7 +207,7 @@ class Company(NestedSet): "default_expense_account": "Cost of Goods Sold" }) - if self.update_default_account or frappe.flags.in_test: + if self.update_default_account: for default_account in default_accounts: self._set_default_account(default_account, default_accounts.get(default_account)) From dec5bead9c4c553e627a1f8087267b02d39b5c28 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 6 Nov 2019 13:13:31 +0530 Subject: [PATCH 054/131] fix: unknown column credit limit in accounts receivable report --- .../accounts_receivable/accounts_receivable.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 228be18d21..11e13b5208 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -79,13 +79,21 @@ frappe.query_reports["Accounts Receivable"] = { "options": "Customer", on_change: () => { var customer = frappe.query_report.get_filter_value('customer'); + var company = frappe.query_report.get_filter_value('company'); if (customer) { - frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "credit_limit", "payment_terms"], function(value) { + frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "payment_terms"], function(value) { frappe.query_report.set_filter_value('tax_id', value["tax_id"]); frappe.query_report.set_filter_value('customer_name', value["customer_name"]); - frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]); }); + + frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, + ["credit_limit"], function(value) { + if (value) { + debugger + frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); + } + }, "Customer"); } else { frappe.query_report.set_filter_value('tax_id', ""); frappe.query_report.set_filter_value('customer_name', ""); From 01c19d3b933907d124e112e5cb8f30dff8f126dd Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 6 Nov 2019 14:41:16 +0530 Subject: [PATCH 055/131] internal issues (#19496) * fix: user remark mandatory in quick entry but not in form * fix: preview salary slip btn showing in draft and unsaved states * minor: removed unecessay comma * fix: department analytics report showing departments of all companies --- .../accounts/doctype/journal_entry/journal_entry.js | 2 +- .../hr/doctype/salary_structure/salary_structure.js | 10 ++++++---- .../hr/doctype/salary_structure/salary_structure.py | 7 ++++++- .../department_analytics/department_analytics.js | 10 ++++++++++ .../department_analytics/department_analytics.py | 9 ++++++++- .../doctype/production_plan/production_plan.js | 2 +- 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 11d847d821..221e3a7280 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -570,7 +570,7 @@ $.extend(erpnext.journal_entry, { }, {fieldtype: "Date", fieldname: "posting_date", label: __("Date"), reqd: 1, default: frm.doc.posting_date}, - {fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark"), reqd: 1}, + {fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark")}, {fieldtype: "Select", fieldname: "naming_series", label: __("Series"), reqd: 1, options: naming_series_options, default: naming_series_default}, ] diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index d56320a073..dd34ef2ae2 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -46,10 +46,12 @@ frappe.ui.form.on('Salary Structure', { frm.trigger("toggle_fields"); frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false); frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false); - - frm.add_custom_button(__("Preview Salary Slip"), function() { - frm.trigger('preview_salary_slip'); - }); + + if(frm.doc.docstatus === 1) { + frm.add_custom_button(__("Preview Salary Slip"), function() { + frm.trigger('preview_salary_slip'); + }); + } if(frm.doc.docstatus==1) { frm.add_custom_button(__("Assign Salary Structure"), function() { diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py index f7d712d3f1..0e1a74f370 100644 --- a/erpnext/hr/doctype/salary_structure/salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/salary_structure.py @@ -169,5 +169,10 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print = @frappe.whitelist() def get_employees(salary_structure): employees = frappe.get_list('Salary Structure Assignment', - filters={'salary_structure': salary_structure}, fields=['employee']) + filters={'salary_structure': salary_structure, 'docstatus': 1}, fields=['employee']) + + if not employees: + frappe.throw(_("There's no Employee with Salary Structure: {0}. \ + Assign {1} to an Employee to preview Salary Slip").format(salary_structure, salary_structure)) + return list(set([d.employee for d in employees])) diff --git a/erpnext/hr/report/department_analytics/department_analytics.js b/erpnext/hr/report/department_analytics/department_analytics.js index a0b6fc7641..29fedcd735 100644 --- a/erpnext/hr/report/department_analytics/department_analytics.js +++ b/erpnext/hr/report/department_analytics/department_analytics.js @@ -2,4 +2,14 @@ // For license information, please see license.txt frappe.query_reports["Department Analytics"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + ] }; \ No newline at end of file diff --git a/erpnext/hr/report/department_analytics/department_analytics.py b/erpnext/hr/report/department_analytics/department_analytics.py index c4a9030c59..b28eac43f8 100644 --- a/erpnext/hr/report/department_analytics/department_analytics.py +++ b/erpnext/hr/report/department_analytics/department_analytics.py @@ -7,6 +7,10 @@ from frappe import _ def execute(filters=None): if not filters: filters = {} + + if not filters["company"]: + frappe.throw(_('{0} is mandatory').format(_('Company'))) + columns = get_columns() employees = get_employees(filters) departments_result = get_department(filters) @@ -28,6 +32,9 @@ def get_conditions(filters): conditions = "" if filters.get("department"): conditions += " and department = '%s'" % \ filters["department"].replace("'", "\\'") + + if filters.get("company"): conditions += " and company = '%s'" % \ + filters["company"].replace("'", "\\'") return conditions def get_employees(filters): @@ -37,7 +44,7 @@ def get_employees(filters): gender, company from `tabEmployee` where status = 'Active' %s""" % conditions, as_list=1) def get_department(filters): - return frappe.db.sql("""select name from `tabDepartment`""" , as_list=1) + return frappe.db.sql("""select name from `tabDepartment` where company = %s""", (filters["company"]), as_list=1) def get_chart_data(departments,employees): if not departments: diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 4b654b47e6..96bb0ae37f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -233,7 +233,7 @@ frappe.ui.form.on('Production Plan', { if (item_wise_qty) { for (var key in item_wise_qty) { - title += __('Item {0}: {1} qty produced, ', [key, item_wise_qty[key]]); + title += __('Item {0}: {1} qty produced. ', [key, item_wise_qty[key]]); } } From e123ec6b452bfa34ff49933d3929572588dc4b0a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 6 Nov 2019 15:25:00 +0530 Subject: [PATCH 056/131] fix: incorrect number of entries while making deferred revenue entry (#19473) --- erpnext/accounts/deferred_revenue.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 32485a3469..62a8f05c65 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -174,6 +174,8 @@ def make_gl_entries(doc, credit_account, debit_account, against, # GL Entry for crediting the amount in the deferred expense from erpnext.accounts.general_ledger import make_gl_entries + if amount == 0: return + gl_entries = [] gl_entries.append( doc.get_gl_dict({ From 6a50c920878b503370116ec7aff9e0401038d082 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 6 Nov 2019 16:34:17 +0530 Subject: [PATCH 057/131] Update accounts_receivable.js --- .../accounts/report/accounts_receivable/accounts_receivable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 11e13b5208..9b4dda2f69 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -90,7 +90,6 @@ frappe.query_reports["Accounts Receivable"] = { frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, ["credit_limit"], function(value) { if (value) { - debugger frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); } }, "Customer"); From d743583bf43d6e059c3402760e74a88359d5d2e5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 Nov 2019 18:03:03 +0530 Subject: [PATCH 058/131] fix: contact creation and fetching for shopping cart (#19510) --- erpnext/shopping_cart/cart.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index a4c10cfb7d..a0ac791619 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -11,6 +11,7 @@ from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings from frappe.utils.nestedset import get_root_of from erpnext.accounts.utils import get_account_name from erpnext.utilities.product import get_qty_in_stock +from frappe.contacts.doctype.contact.contact import get_contact_name class WebsitePriceListMissingError(frappe.ValidationError): @@ -371,7 +372,7 @@ def get_party(user=None): if not user: user = frappe.session.user - contact_name = frappe.db.get_value("Contact", {"email_id": user}) + contact_name = get_contact_name(user) party = None if contact_name: @@ -417,7 +418,7 @@ def get_party(user=None): contact = frappe.new_doc("Contact") contact.update({ "first_name": fullname, - "email_id": user + "email_ids": [{"email_id": user, "is_primary": 1}] }) contact.append('links', dict(link_doctype='Customer', link_name=customer.name)) contact.flags.ignore_mandatory = True From 1beed7db72f8702dc2b271e4b601396488f62ae3 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 6 Nov 2019 18:12:29 +0530 Subject: [PATCH 059/131] fix: Added 'status' field in Payment Entry (#19507) --- .../accounts/doctype/payment_entry/payment_entry.json | 11 ++++++++++- .../accounts/doctype/payment_entry/payment_entry.py | 11 +++++++++++ erpnext/patches.txt | 1 + erpnext/patches/v12_0/set_payment_entry_status.py | 8 ++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/set_payment_entry_status.py diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index a85eccd30a..acfc660c4f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -62,6 +62,7 @@ "dimension_col_break", "cost_center", "section_break_12", + "status", "remarks", "column_break_16", "letter_head", @@ -563,10 +564,18 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nDraft\nSubmitted\nCancelled", + "read_only": 1 } ], "is_submittable": 1, - "modified": "2019-05-27 15:53:21.108857", + "modified": "2019-11-06 12:59:43.151721", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 89aaffbc2d..bf7e833285 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -61,6 +61,7 @@ class PaymentEntry(AccountsController): self.validate_duplicate_entry() self.validate_allocated_amount() self.ensure_supplier_is_not_blocked() + self.set_status() def on_submit(self): self.setup_party_account_field() @@ -70,6 +71,7 @@ class PaymentEntry(AccountsController): self.update_outstanding_amounts() self.update_advance_paid() self.update_expense_claim() + self.set_status() def on_cancel(self): @@ -79,6 +81,7 @@ class PaymentEntry(AccountsController): self.update_advance_paid() self.update_expense_claim() self.delink_advance_entry_references() + self.set_status() def update_outstanding_amounts(self): self.set_missing_ref_details(force=True) @@ -275,6 +278,14 @@ class PaymentEntry(AccountsController): frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry") .format(d.reference_name, dr_or_cr)) + def set_status(self): + if self.docstatus == 2: + self.status = 'Cancelled' + elif self.docstatus == 1: + self.status = 'Submitted' + else: + self.status = 'Draft' + def set_amounts(self): self.set_amounts_in_company_currency() self.set_total_allocated_amount() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ee6bdff661..cc00b7e412 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -641,3 +641,4 @@ erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings +erpnext.patches.v12_0.set_payment_entry_status diff --git a/erpnext/patches/v12_0/set_payment_entry_status.py b/erpnext/patches/v12_0/set_payment_entry_status.py new file mode 100644 index 0000000000..af95ed7e8b --- /dev/null +++ b/erpnext/patches/v12_0/set_payment_entry_status.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + frappe.db.sql("""update `tabPayment Entry` set status = CASE + WHEN docstatus = 1 THEN 'Submitted' + WHEN docstatus = 2 THEN 'Cancelled' + ELSE 'Draft' + END;""") \ No newline at end of file From a709ae894cad8de72e051543ad7af405148c07ff Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 6 Nov 2019 18:20:06 +0530 Subject: [PATCH 060/131] feat: duplicate linked task in project (#19271) * feat: create a duplicate project * fix: allow duplication via form * feat: fetch old task and link project * fix: link task with project * fix: parse json string as python object * fix: avoid duplicate task based on the project template * fix: ask user for the new project name * fix: display a descriptive message on switching to a new route * fix: override duplicate in menu * fix: check for duplicate project name after submitting prompt * fix: set the project template * fix: minor changes * fix: function call * refactor: add a separate button for duplicate * Update project.js --- erpnext/projects/doctype/project/project.js | 30 ++++++++++++++++---- erpnext/projects/doctype/project/project.py | 31 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 192e1bc2a5..4a03a58f80 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -19,7 +19,7 @@ frappe.ui.form.on("Project", { frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); }, - } + }; }, onload: function (frm) { var so = frappe.meta.get_docfield("Project", "sales_order"); @@ -28,15 +28,15 @@ frappe.ui.form.on("Project", { return { "customer": frm.doc.customer, "project_name": frm.doc.name - } - } + }; + }; frm.set_query('customer', 'erpnext.controllers.queries.customer_query'); frm.set_query("user", "users", function () { return { query: "erpnext.projects.doctype.project.project.get_users_for_project" - } + }; }); // sales order @@ -51,7 +51,7 @@ frappe.ui.form.on("Project", { return { filters: filters - } + }; }); if (frappe.model.can_read("Task")) { @@ -85,6 +85,10 @@ frappe.ui.form.on("Project", { set_buttons: function(frm) { if (!frm.is_new()) { + frm.add_custom_button(__('Duplicate Project with Tasks'), () => { + frm.events.create_duplicate(frm); + }); + frm.add_custom_button(__('Completed'), () => { frm.events.set_status(frm, 'Completed'); }, __('Set Status')); @@ -95,6 +99,22 @@ frappe.ui.form.on("Project", { } }, + create_duplicate: function(frm) { + return new Promise(resolve => { + frappe.prompt('Project Name', (data) => { + frappe.xcall('erpnext.projects.doctype.project.project.create_duplicate_project', + { + prev_doc: frm.doc, + project_name: data.value + }).then(() => { + frappe.set_route('Form', "Project", data.value); + frappe.show_alert(__("Duplicate project has been created")); + }); + resolve(); + }); + }); + }, + set_status: function(frm, status) { frappe.confirm(__('Set Project and all Tasks to status {0}?', [status.bold()]), () => { frappe.xcall('erpnext.projects.doctype.project.project.set_project_status', diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 783bcf3c38..bf6e21aa4d 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -323,6 +323,37 @@ def allow_to_make_project_update(project, time, frequency): if get_time(nowtime()) >= get_time(time): return True + +@frappe.whitelist() +def create_duplicate_project(prev_doc, project_name): + ''' Create duplicate project based on the old project ''' + import json + prev_doc = json.loads(prev_doc) + + if project_name == prev_doc.get('name'): + frappe.throw(_("Use a name that is different from previous project name")) + + # change the copied doc name to new project name + project = frappe.copy_doc(prev_doc) + project.name = project_name + project.project_template = '' + project.project_name = project_name + project.insert() + + # fetch all the task linked with the old project + task_list = frappe.get_all("Task", filters={ + 'project': prev_doc.get('name') + }, fields=['name']) + + # Create duplicate task for all the task + for task in task_list: + task = frappe.get_doc('Task', task) + new_task = frappe.copy_doc(task) + new_task.project = project.name + new_task.insert() + + project.db_set('project_template', prev_doc.get('project_template')) + def get_projects_for_collect_progress(frequency, fields): fields.extend(["name"]) From 7508896bfb7ac3cd3ccc81634786fb680aa0c8f2 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 6 Nov 2019 18:46:57 +0530 Subject: [PATCH 061/131] perf: Optimise BOM Update Tool (#19236) * perf: Optimize BOM update tool - Remove redundant traverse_tree calls - Cache bom_children data Co-authored-by: Rohit Waghchaure * fix: Replace get_list with db.sql * fix: Enable versioning for updated BOM * fix: Directly save doc_before_save from bom obj instead of using load_doc_before_save * fix: recurssion check performance issue --- erpnext/manufacturing/doctype/bom/bom.py | 32 ++++++++++++++----- .../bom_update_tool/bom_update_tool.py | 30 ++++++++--------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c849f5b7f2..a1bf35fd08 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -420,8 +420,12 @@ class BOM(WebsiteGenerator): def traverse_tree(self, bom_list=None): def _get_children(bom_no): - return frappe.db.sql_list("""select bom_no from `tabBOM Item` - where parent = %s and ifnull(bom_no, '') != '' and parenttype='BOM'""", bom_no) + children = frappe.cache().hget('bom_children', bom_no) + if children is None: + children = frappe.db.sql_list("""SELECT `bom_no` FROM `tabBOM Item` + WHERE `parent`=%s AND `bom_no`!='' AND `parenttype`='BOM'""", bom_no) + frappe.cache().hset('bom_children', bom_no, children) + return children count = 0 if not bom_list: @@ -534,12 +538,24 @@ class BOM(WebsiteGenerator): def get_child_exploded_items(self, bom_no, stock_qty): """ Add all items from Flat BOM of child BOM""" # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss - child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name, - bom_item.description, bom_item.source_warehouse, bom_item.operation, - bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, bom_item.include_item_in_manufacturing, - bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit - from `tabBOM Explosion Item` bom_item, tabBOM bom - where bom_item.parent = bom.name and bom.name = %s and bom.docstatus = 1""", bom_no, as_dict = 1) + child_fb_items = frappe.db.sql(""" + SELECT + bom_item.item_code, + bom_item.item_name, + bom_item.description, + bom_item.source_warehouse, + bom_item.operation, + bom_item.stock_uom, + bom_item.stock_qty, + bom_item.rate, + bom_item.include_item_in_manufacturing, + bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit + FROM `tabBOM Explosion Item` bom_item, tabBOM bom + WHERE + bom_item.parent = bom.name + AND bom.name = %s + AND bom.docstatus = 1 + """, bom_no, as_dict = 1) for d in child_fb_items: self.add_to_cur_exploded_items(frappe._dict({ diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 87b8f67e53..2ca4d16a07 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -14,23 +14,23 @@ class BOMUpdateTool(Document): def replace_bom(self): self.validate_bom() self.update_new_bom() + frappe.cache().delete_key('bom_children') bom_list = self.get_parent_boms(self.new_bom) updated_bom = [] for bom in bom_list: try: - bom_obj = frappe.get_doc("BOM", bom) - bom_obj.load_doc_before_save() - updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom) + bom_obj = frappe.get_cached_doc('BOM', bom) + # this is only used for versioning and we do not want + # to make separate db calls by using load_doc_before_save + # which proves to be expensive while doing bulk replace + bom_obj._doc_before_save = bom_obj.as_dict() bom_obj.calculate_cost() bom_obj.update_parent_cost() bom_obj.db_update() - if (getattr(bom_obj.meta, 'track_changes', False) and not bom_obj.flags.ignore_version): + if bom_obj.meta.get('track_changes') and not bom_obj.flags.ignore_version: bom_obj.save_version() - - frappe.db.commit() except Exception: - frappe.db.rollback() frappe.log_error(frappe.get_traceback()) def validate_bom(self): @@ -42,22 +42,22 @@ class BOMUpdateTool(Document): frappe.throw(_("The selected BOMs are not for the same item")) def update_new_bom(self): - new_bom_unitcost = frappe.db.sql("""select total_cost/quantity - from `tabBOM` where name = %s""", self.new_bom) + new_bom_unitcost = frappe.db.sql("""SELECT `total_cost`/`quantity` + FROM `tabBOM` WHERE name = %s""", self.new_bom) new_bom_unitcost = flt(new_bom_unitcost[0][0]) if new_bom_unitcost else 0 frappe.db.sql("""update `tabBOM Item` set bom_no=%s, rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""", (self.new_bom, new_bom_unitcost, new_bom_unitcost, self.current_bom)) - def get_parent_boms(self, bom, bom_list=None): - if not bom_list: - bom_list = [] - - data = frappe.db.sql(""" select distinct parent from `tabBOM Item` - where bom_no = %s and docstatus < 2 and parenttype='BOM'""", bom) + def get_parent_boms(self, bom, bom_list=[]): + data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item` + WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom) for d in data: + if self.new_bom == d[0]: + frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(bom, self.new_bom)) + bom_list.append(d[0]) self.get_parent_boms(d[0], bom_list) From a5776d16b4da69892b0552d1437672448c8bd4f7 Mon Sep 17 00:00:00 2001 From: Mitchy25 <42224026+Mitchy25@users.noreply.github.com> Date: Thu, 7 Nov 2019 02:34:54 +1300 Subject: [PATCH 062/131] fix: Version 12 Bank Reconciliation fix (#19182) * Fix Bank Reconciliation Change fixes Bank Rec upload as well as the duplicatation of bank_account and company filters * Update bank_reconciliation.js Removing incorrect fix to filters. Filters are loaded from Bank Transaction List. * Update bank_reconciliation.js --- .../bank_reconciliation/bank_reconciliation.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 6eafa0d231..854b973bea 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -139,15 +139,11 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { } make() { - const me = this; - frappe.upload.make({ - args: { - method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', - allow_multiple: 0 - }, - no_socketio: true, - sample_url: "e.g. http://example.com/somefile.csv", - callback: function(attachment, r) { + const me = this; + new frappe.ui.FileUploader({ + method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', + allow_multiple: 0, + on_success: function(attachment, r) { if (!r.exc && r.message) { me.data = r.message; me.setup_transactions_dom(); @@ -575,4 +571,4 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { } } -} \ No newline at end of file +} From edba06038e1b6a03b60643e98ab63ce746964fd9 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 6 Nov 2019 14:37:04 +0100 Subject: [PATCH 063/131] feat(regional): enable transaction log for germany (#19198) --- erpnext/hooks.py | 5 ++--- erpnext/regional/__init__.py | 18 ++++++++++++++++++ erpnext/regional/france/utils.py | 16 ---------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index b1855ec9bb..9e74bfd290 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -235,17 +235,16 @@ doc_events = { ("Sales Taxes and Charges Template", 'Price List'): { "on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings" }, - "Website Settings": { "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" }, "Sales Invoice": { - "on_submit": ["erpnext.regional.france.utils.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], + "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", "on_trash": "erpnext.regional.check_deletion_permission" }, "Payment Entry": { - "on_submit": ["erpnext.regional.france.utils.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.make_status_as_paid"], + "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.make_status_as_paid"], "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 7388ea0e72..630d5fae2a 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -10,3 +10,21 @@ def check_deletion_permission(doc, method): region = get_region(doc.company) if region in ["Nepal", "France"] and doc.docstatus != 0: frappe.throw(_("Deletion is not permitted for country {0}".format(region))) + +def create_transaction_log(doc, method): + """ + Appends the transaction to a chain of hashed logs for legal resons. + Called on submit of Sales Invoice and Payment Entry. + """ + region = get_region() + if region not in ["France", "Germany"]: + return + + data = str(doc.as_dict()) + + frappe.get_doc({ + "doctype": "Transaction Log", + "reference_doctype": doc.doctype, + "document_name": doc.name, + "data": data + }).insert(ignore_permissions=True) diff --git a/erpnext/regional/france/utils.py b/erpnext/regional/france/utils.py index e4b72f6586..424615dbbc 100644 --- a/erpnext/regional/france/utils.py +++ b/erpnext/regional/france/utils.py @@ -3,22 +3,6 @@ from __future__ import unicode_literals import frappe -from frappe import _ -from erpnext import get_region - -def create_transaction_log(doc, method): - region = get_region() - if region not in ["France"]: - return - else: - data = str(doc.as_dict()) - - frappe.get_doc({ - "doctype": "Transaction Log", - "reference_doctype": doc.doctype, - "document_name": doc.name, - "data": data - }).insert(ignore_permissions=True) # don't remove this function it is used in tests def test_method(): From 36520ef951064a8a0bafa66b1cf1ec2115320f7e Mon Sep 17 00:00:00 2001 From: theopen-institute Date: Wed, 6 Nov 2019 19:24:15 +0545 Subject: [PATCH 064/131] fix: Add a setting to make creation of new Student User optional (#19122) * New setting to skip Student User creation * Allow skip of new User creation via settings --- .../doctype/education_settings/education_settings.json | 10 +++++++++- erpnext/education/doctype/student/student.py | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/education/doctype/education_settings/education_settings.json b/erpnext/education/doctype/education_settings/education_settings.json index 32b5fb8198..967a030fd2 100644 --- a/erpnext/education/doctype/education_settings/education_settings.json +++ b/erpnext/education/doctype/education_settings/education_settings.json @@ -11,6 +11,7 @@ "validate_batch", "validate_course", "academic_term_reqd", + "user_creation_skip", "section_break_7", "instructor_created_by", "web_academy_settings_section", @@ -91,6 +92,13 @@ "fieldname": "enable_lms", "fieldtype": "Check", "label": "Enable LMS" + }, + { + "default": "0", + "description": "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.", + "fieldname": "user_creation_skip", + "fieldtype": "Check", + "label": "Skip User creation for new Student" } ], "issingle": 1, @@ -133,4 +141,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 705c6e4e98..9af5e22913 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -40,7 +40,8 @@ class Student(Document): frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant)) def after_insert(self): - self.create_student_user() + if not frappe.get_single('Education Settings').user_creation_skip: + self.create_student_user() def create_student_user(self): """Create a website user for student creation if not already exists""" From 1c44bb09820e9f2fe0d80d64f005047a7ed04e3f Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Thu, 7 Nov 2019 12:38:05 +0530 Subject: [PATCH 065/131] fix(cart): return rule instead of rule_label_map --- erpnext/shopping_cart/cart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index a0ac791619..1236ade45f 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -505,7 +505,7 @@ def get_applicable_shipping_rules(party=None, quotation=None): if shipping_rules: rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label") # we need this in sorted order as per the position of the rule in the settings page - return [[rule, rule_label_map.get(rule)] for rule in shipping_rules] + return [[rule, rule] for rule in shipping_rules] def get_shipping_rules(quotation=None, cart_settings=None): if not quotation: @@ -563,4 +563,4 @@ def apply_coupon_code(applied_code,applied_referral_sales_partner): frappe.throw(_("Please enter valid coupon code !!")) else: frappe.throw(_("Please enter coupon code !!")) - return quotation \ No newline at end of file + return quotation From e37a67245e5bc9e1002041caf032c67a9066e6a4 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 7 Nov 2019 12:52:23 +0530 Subject: [PATCH 066/131] fix: Sales and Purchase Invoice Status, Payment Reconciliation Credit/Debit Note (#19388) --- .../doctype/payment_reconciliation/payment_reconciliation.py | 3 ++- .../doctype/purchase_invoice/purchase_invoice_list.js | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- erpnext/controllers/status_updater.py | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 4665d75510..d85344e8b7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -90,7 +90,8 @@ class PaymentReconciliation(Document): FROM `tab{doc}`, `tabGL Entry` WHERE (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) - and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s + and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL + and `tabGL Entry`.against_voucher_type = %(voucher_type)s and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s GROUP BY `tab{doc}`.name diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 4e76a8d955..800ed921bd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -6,8 +6,8 @@ frappe.listview_settings['Purchase Invoice'] = { add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", "currency", "is_return", "release_date", "on_hold"], get_indicator: function(doc) { - if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) { - return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"] + if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { + return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; } else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { if(cint(doc.on_hold) && !doc.release_date) { return [__("On Hold"), "darkgrey"]; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 5766c9a8d1..0ebca8bf51 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1230,7 +1230,8 @@ class SalesInvoice(SellingController): self.status = "Unpaid and Discounted" elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()): self.status = "Unpaid" - elif flt(self.outstanding_amount) < 0 and self.is_return==0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): + #Check if outstanding amount is 0 due to credit note issued against invoice + elif flt(self.outstanding_amount) <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): self.status = "Credit Note Issued" elif self.is_return == 1: self.status = "Return" diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 9d1389c977..2b2c27b052 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -49,7 +49,8 @@ status_map = { ["Submitted", "eval:self.docstatus==1"], ["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"], ["Return", "eval:self.is_return==1 and self.docstatus==1"], - ["Debit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1"], + ["Debit Note Issued", + "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"], ["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"], ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"], ["Cancelled", "eval:self.docstatus==2"], @@ -118,7 +119,6 @@ class StatusUpdater(Document): if self.doctype in status_map: _status = self.status - if status and update: self.db_set("status", status) From 52d888de42af03a43e8f2b99736e765e562f85e5 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 7 Nov 2019 13:01:45 +0530 Subject: [PATCH 067/131] fix[minor]: Payment Entry status patch (#19519) --- erpnext/patches/v12_0/set_payment_entry_status.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/set_payment_entry_status.py b/erpnext/patches/v12_0/set_payment_entry_status.py index af95ed7e8b..fafbec6a9a 100644 --- a/erpnext/patches/v12_0/set_payment_entry_status.py +++ b/erpnext/patches/v12_0/set_payment_entry_status.py @@ -1,6 +1,7 @@ import frappe def execute(): + frappe.reload_doctype("Payment Entry") frappe.db.sql("""update `tabPayment Entry` set status = CASE WHEN docstatus = 1 THEN 'Submitted' WHEN docstatus = 2 THEN 'Cancelled' From 28c6a0aeb1da8453ae48982a4e9d40ebda445118 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 7 Nov 2019 14:16:26 +0530 Subject: [PATCH 068/131] fix: reload global defaults (#19517) --- .../v11_0/update_delivery_trip_status.py | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/erpnext/patches/v11_0/update_delivery_trip_status.py b/erpnext/patches/v11_0/update_delivery_trip_status.py index 64b3063bac..42f017e04d 100755 --- a/erpnext/patches/v11_0/update_delivery_trip_status.py +++ b/erpnext/patches/v11_0/update_delivery_trip_status.py @@ -1,27 +1,28 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('stock', 'doctype', 'delivery_trip') - frappe.reload_doc('stock', 'doctype', 'delivery_stop', force=True) - - for trip in frappe.get_all("Delivery Trip"): - trip_doc = frappe.get_doc("Delivery Trip", trip.name) - - status = { - 0: "Draft", - 1: "Scheduled", - 2: "Cancelled" - }[trip_doc.docstatus] - - if trip_doc.docstatus == 1: - visited_stops = [stop.visited for stop in trip_doc.delivery_stops] - if all(visited_stops): - status = "Completed" - elif any(visited_stops): - status = "In Transit" - - frappe.db.set_value("Delivery Trip", trip.name, "status", status, update_modified=False) +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('setup', 'doctype', 'global_defaults', force=True) + frappe.reload_doc('stock', 'doctype', 'delivery_trip') + frappe.reload_doc('stock', 'doctype', 'delivery_stop', force=True) + + for trip in frappe.get_all("Delivery Trip"): + trip_doc = frappe.get_doc("Delivery Trip", trip.name) + + status = { + 0: "Draft", + 1: "Scheduled", + 2: "Cancelled" + }[trip_doc.docstatus] + + if trip_doc.docstatus == 1: + visited_stops = [stop.visited for stop in trip_doc.delivery_stops] + if all(visited_stops): + status = "Completed" + elif any(visited_stops): + status = "In Transit" + + frappe.db.set_value("Delivery Trip", trip.name, "status", status, update_modified=False) From bac50bc295c514148463e624cc727d4f7cf95abb Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 7 Nov 2019 18:04:50 +0530 Subject: [PATCH 069/131] fix: remove validate for issue priority (#19522) --- erpnext/support/doctype/issue_priority/issue_priority.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/support/doctype/issue_priority/issue_priority.py b/erpnext/support/doctype/issue_priority/issue_priority.py index cecaaaab29..7c8925ebc3 100644 --- a/erpnext/support/doctype/issue_priority/issue_priority.py +++ b/erpnext/support/doctype/issue_priority/issue_priority.py @@ -8,7 +8,4 @@ from frappe import _ from frappe.model.document import Document class IssuePriority(Document): - - def validate(self): - if frappe.db.exists("Issue Priority", {"name": self.name}): - frappe.throw(_("Issue Priority Already Exists")) + pass \ No newline at end of file From 628701f1a52473f9e762255d8f745e104d88b313 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 7 Nov 2019 18:05:40 +0530 Subject: [PATCH 070/131] fix: Accounts mandatory depending on share transfer type (#19523) --- .../doctype/share_transfer/share_transfer.js | 6 + .../share_transfer/share_transfer.json | 1048 ++++------------- 2 files changed, 209 insertions(+), 845 deletions(-) diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.js b/erpnext/accounts/doctype/share_transfer/share_transfer.js index 364ca6fd28..1cad4dfae3 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.js +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.js @@ -21,6 +21,8 @@ frappe.ui.form.on('Share Transfer', { erpnext.share_transfer.make_jv(frm); }); } + + frm.toggle_reqd("asset_account", frm.doc.transfer_type != "Transfer"); }, no_of_shares: (frm) => { if (frm.doc.rate != undefined || frm.doc.rate != null){ @@ -56,6 +58,10 @@ frappe.ui.form.on('Share Transfer', { }; }); } + }, + + transfer_type: function(frm) { + frm.toggle_reqd("asset_account", frm.doc.transfer_type != "Transfer"); } }); diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.json b/erpnext/accounts/doctype/share_transfer/share_transfer.json index 24c4569b00..f17bf04caf 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.json +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.json @@ -1,881 +1,239 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "ACC-SHT-.YYYY.-.#####", - "beta": 0, - "creation": "2017-12-25 17:18:03.143726", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "ACC-SHT-.YYYY.-.#####", + "creation": "2017-12-25 17:18:03.143726", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "transfer_type", + "column_break_1", + "date", + "section_break_1", + "from_shareholder", + "from_folio_no", + "column_break_3", + "to_shareholder", + "to_folio_no", + "section_break_10", + "equity_or_liability_account", + "column_break_12", + "asset_account", + "section_break_4", + "share_type", + "from_no", + "rate", + "column_break_8", + "no_of_shares", + "to_no", + "amount", + "section_break_11", + "company", + "section_break_6", + "remarks", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transfer_type", - "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": "Transfer Type", - "length": 0, - "no_copy": 0, - "options": "\nIssue\nPurchase\nTransfer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "transfer_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Transfer Type", + "options": "\nIssue\nPurchase\nTransfer", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_1", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_1", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.transfer_type != 'Issue'", - "fieldname": "from_shareholder", - "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": "From Shareholder", - "length": 0, - "no_copy": 0, - "options": "Shareholder", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.transfer_type != 'Issue'", + "fieldname": "from_shareholder", + "fieldtype": "Link", + "label": "From Shareholder", + "options": "Shareholder" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.transfer_type != 'Issue'", - "fetch_from": "from_shareholder.folio_no", - "fieldname": "from_folio_no", - "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": "From Folio No", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.transfer_type != 'Issue'", + "fetch_from": "from_shareholder.folio_no", + "fieldname": "from_folio_no", + "fieldtype": "Data", + "label": "From Folio No" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.company", - "fieldname": "equity_or_liability_account", - "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": "Equity/Liability Account", - "length": 0, - "no_copy": 0, - "options": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.company", + "fieldname": "equity_or_liability_account", + "fieldtype": "Link", + "label": "Equity/Liability Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.transfer_type != 'Transfer') && (doc.company)", - "fieldname": "asset_account", - "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 Account", - "length": 0, - "no_copy": 0, - "options": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:(doc.transfer_type != 'Transfer') && (doc.company)", + "fieldname": "asset_account", + "fieldtype": "Link", + "label": "Asset Account", + "options": "Account" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.transfer_type != 'Purchase'", - "fieldname": "to_shareholder", - "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": "To Shareholder", - "length": 0, - "no_copy": 0, - "options": "Shareholder", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.transfer_type != 'Purchase'", + "fieldname": "to_shareholder", + "fieldtype": "Link", + "label": "To Shareholder", + "options": "Shareholder" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.transfer_type != 'Purchase'", - "fetch_from": "to_shareholder.folio_no", - "fieldname": "to_folio_no", - "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": "To Folio No", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.transfer_type != 'Purchase'", + "fetch_from": "to_shareholder.folio_no", + "fieldname": "to_folio_no", + "fieldtype": "Data", + "label": "To Folio No" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "share_type", - "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": "Share Type", - "length": 0, - "no_copy": 0, - "options": "Share 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "share_type", + "fieldtype": "Link", + "label": "Share Type", + "options": "Share Type", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "(including)", - "fieldname": "from_no", - "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": "From No", - "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, - "translatable": 0, - "unique": 0 - }, + "description": "(including)", + "fieldname": "from_no", + "fieldtype": "Int", + "label": "From No", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "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": "Rate", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "no_of_shares", - "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": "No of Shares", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "no_of_shares", + "fieldtype": "Int", + "label": "No of Shares", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "(including)", - "fieldname": "to_no", - "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": "To No", - "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, - "translatable": 0, - "unique": 0 - }, + "description": "(including)", + "fieldname": "to_no", + "fieldtype": "Int", + "label": "To No", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": "Amount", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_11", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "remarks", - "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": "Remarks", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "remarks", + "fieldtype": "Long Text", + "label": "Remarks" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 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": "Share Transfer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Share Transfer", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" } - ], - "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": "2018-09-18 14:14:46.233568", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Share Transfer", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "modified": "2019-11-07 13:31:17.999744", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Share Transfer", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "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": 1, + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 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": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, "write": 1 - }, + }, { - "amend": 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": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 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", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From b455318f01040b9ddbfc239e25905f4064b9ad26 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 7 Nov 2019 18:20:32 +0530 Subject: [PATCH 071/131] fix(Integration): Woocommerce issues (#19487) * fix: Delivery URL returned response code 500 * fix: set default company in Woocommerce Settings * fix: remove redundant function calls * fix: make offset configurable for delivery date in sales order * fix: remove redundant code from woocommerce_settings.py * fix: import create_custom_field * fix: added ignore_mandatory for saving item, customer and sales order * fix: remove unused woocommerce_check custom field * fix: do not delete custom fields or item group when sync is disabled --- .../connectors/woocommerce_connection.py | 254 +++---- .../woocommerce_settings.json | 639 ++---------------- .../woocommerce_settings.py | 75 +- erpnext/tests/test_woocommerce.py | 1 + 4 files changed, 177 insertions(+), 792 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 0b6ea8cc7c..28c2ab9e54 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -1,10 +1,8 @@ from __future__ import unicode_literals import frappe, base64, hashlib, hmac, json -import datetime from frappe import _ - def verify_request(): woocommerce_settings = frappe.get_doc("Woocommerce Settings") sig = base64.b64encode( @@ -30,191 +28,149 @@ def order(*args, **kwargs): frappe.log_error(error_message, "WooCommerce Error") raise - def _order(*args, **kwargs): woocommerce_settings = frappe.get_doc("Woocommerce Settings") if frappe.flags.woocomm_test_order_data: - fd = frappe.flags.woocomm_test_order_data + order = frappe.flags.woocomm_test_order_data event = "created" elif frappe.request and frappe.request.data: verify_request() - fd = json.loads(frappe.request.data) + try: + order = json.loads(frappe.request.data) + except ValueError: + #woocommerce returns 'webhook_id=value' for the first request which is not JSON + order = frappe.request.data event = frappe.get_request_header("X-Wc-Webhook-Event") else: return "success" if event == "created": - raw_billing_data = fd.get("billing") - customer_woo_com_email = raw_billing_data.get("email") - - if frappe.get_value("Customer",{"woocommerce_email": customer_woo_com_email}): - # Edit - link_customer_and_address(raw_billing_data,1) - else: - # Create - link_customer_and_address(raw_billing_data,0) - - - items_list = fd.get("line_items") - for item in items_list: - - item_woo_com_id = item.get("product_id") - - if frappe.get_value("Item",{"woocommerce_id": item_woo_com_id}): - #Edit - link_item(item,1) - else: - link_item(item,0) - - + raw_billing_data = order.get("billing") customer_name = raw_billing_data.get("first_name") + " " + raw_billing_data.get("last_name") + link_customer_and_address(raw_billing_data, customer_name) + link_items(order.get("line_items"), woocommerce_settings) + create_sales_order(order, woocommerce_settings, customer_name) - new_sales_order = frappe.new_doc("Sales Order") - new_sales_order.customer = customer_name - - created_date = fd.get("date_created").split("T") - new_sales_order.transaction_date = created_date[0] - - new_sales_order.po_no = fd.get("id") - new_sales_order.woocommerce_id = fd.get("id") - new_sales_order.naming_series = woocommerce_settings.sales_order_series or "SO-WOO-" - - placed_order_date = created_date[0] - raw_date = datetime.datetime.strptime(placed_order_date, "%Y-%m-%d") - raw_delivery_date = frappe.utils.add_to_date(raw_date,days = 7) - order_delivery_date_str = raw_delivery_date.strftime('%Y-%m-%d') - order_delivery_date = str(order_delivery_date_str) - - new_sales_order.delivery_date = order_delivery_date - default_set_company = frappe.get_doc("Global Defaults") - company = raw_billing_data.get("company") or default_set_company.default_company - found_company = frappe.get_doc("Company",{"name":company}) - company_abbr = found_company.abbr - - new_sales_order.company = company - - for item in items_list: - woocomm_item_id = item.get("product_id") - found_item = frappe.get_doc("Item",{"woocommerce_id": woocomm_item_id}) - - ordered_items_tax = item.get("total_tax") - - new_sales_order.append("items",{ - "item_code": found_item.item_code, - "item_name": found_item.item_name, - "description": found_item.item_name, - "delivery_date":order_delivery_date, - "uom": woocommerce_settings.uom or _("Nos"), - "qty": item.get("quantity"), - "rate": item.get("price"), - "warehouse": woocommerce_settings.warehouse or "Stores" + " - " + company_abbr - }) - - add_tax_details(new_sales_order,ordered_items_tax,"Ordered Item tax",0) - - # shipping_details = fd.get("shipping_lines") # used for detailed order - shipping_total = fd.get("shipping_total") - shipping_tax = fd.get("shipping_tax") - - add_tax_details(new_sales_order,shipping_tax,"Shipping Tax",1) - add_tax_details(new_sales_order,shipping_total,"Shipping Total",1) - - new_sales_order.submit() - - frappe.db.commit() - -def link_customer_and_address(raw_billing_data,customer_status): - - if customer_status == 0: - # create +def link_customer_and_address(raw_billing_data, customer_name): + customer_woo_com_email = raw_billing_data.get("email") + customer_exists = frappe.get_value("Customer", {"woocommerce_email": customer_woo_com_email}) + if not customer_exists: + # Create Customer customer = frappe.new_doc("Customer") - address = frappe.new_doc("Address") - - if customer_status == 1: - # Edit - customer_woo_com_email = raw_billing_data.get("email") - customer = frappe.get_doc("Customer",{"woocommerce_email": customer_woo_com_email}) + else: + # Edit Customer + customer = frappe.get_doc("Customer", {"woocommerce_email": customer_woo_com_email}) old_name = customer.customer_name - full_name = str(raw_billing_data.get("first_name"))+ " "+str(raw_billing_data.get("last_name")) - customer.customer_name = full_name - customer.woocommerce_email = str(raw_billing_data.get("email")) - customer.save() - frappe.db.commit() + customer.customer_name = customer_name + customer.woocommerce_email = customer_woo_com_email + customer.flags.ignore_mandatory = True + customer.save() - if customer_status == 1: - frappe.rename_doc("Customer", old_name, full_name) - address = frappe.get_doc("Address",{"woocommerce_email":customer_woo_com_email}) - customer = frappe.get_doc("Customer",{"woocommerce_email": customer_woo_com_email}) + if customer_exists: + frappe.rename_doc("Customer", old_name, customer_name) + address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email}) + else: + address = frappe.new_doc("Address") address.address_line1 = raw_billing_data.get("address_1", "Not Provided") address.address_line2 = raw_billing_data.get("address_2", "Not Provided") address.city = raw_billing_data.get("city", "Not Provided") - address.woocommerce_email = str(raw_billing_data.get("email")) - address.address_type = "Shipping" - address.country = frappe.get_value("Country", filters={"code":raw_billing_data.get("country", "IN").lower()}) - address.state = raw_billing_data.get("state") - address.pincode = str(raw_billing_data.get("postcode")) - address.phone = str(raw_billing_data.get("phone")) - address.email_id = str(raw_billing_data.get("email")) - + address.woocommerce_email = customer_woo_com_email + address.address_type = "Billing" + address.country = frappe.get_value("Country", {"code": raw_billing_data.get("country", "IN").lower()}) + address.state = raw_billing_data.get("state") + address.pincode = raw_billing_data.get("postcode") + address.phone = raw_billing_data.get("phone") + address.email_id = customer_woo_com_email address.append("links", { "link_doctype": "Customer", "link_name": customer.customer_name }) + address.flags.ignore_mandatory = True + address = address.save() - address.save() - frappe.db.commit() - - if customer_status == 1: - - address = frappe.get_doc("Address",{"woocommerce_email":customer_woo_com_email}) + if customer_exists: old_address_title = address.name - new_address_title = customer.customer_name+"-billing" + new_address_title = customer.customer_name + "-billing" address.address_title = customer.customer_name address.save() - frappe.rename_doc("Address",old_address_title,new_address_title) + frappe.rename_doc("Address", old_address_title, new_address_title) - frappe.db.commit() - -def link_item(item_data,item_status): - woocommerce_settings = frappe.get_doc("Woocommerce Settings") - - if item_status == 0: - #Create Item - item = frappe.new_doc("Item") - - if item_status == 1: - #Edit Item +def link_items(items_list, woocommerce_settings): + for item_data in items_list: item_woo_com_id = item_data.get("product_id") - item = frappe.get_doc("Item",{"woocommerce_id": item_woo_com_id}) - item.item_name = str(item_data.get("name")) - item.item_code = "woocommerce - " + str(item_data.get("product_id")) - item.woocommerce_id = str(item_data.get("product_id")) - item.item_group = _("WooCommerce Products") - item.stock_uom = woocommerce_settings.uom or _("Nos") - item.save() + if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}): + #Edit Item + item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id}) + else: + #Create Item + item = frappe.new_doc("Item") + + item.item_name = item_data.get("name") + item.item_code = _("woocommerce - {0}").format(item_data.get("product_id")) + item.woocommerce_id = item_data.get("product_id") + item.item_group = _("WooCommerce Products") + item.stock_uom = woocommerce_settings.uom or _("Nos") + item.flags.ignore_mandatory = True + item.save() + +def create_sales_order(order, woocommerce_settings, customer_name): + new_sales_order = frappe.new_doc("Sales Order") + new_sales_order.customer = customer_name + + new_sales_order.po_no = new_sales_order.woocommerce_id = order.get("id") + new_sales_order.naming_series = woocommerce_settings.sales_order_series or "SO-WOO-" + + created_date = order.get("date_created").split("T") + new_sales_order.transaction_date = created_date[0] + delivery_after = woocommerce_settings.delivery_after_days or 7 + new_sales_order.delivery_date = frappe.utils.add_days(created_date[0], delivery_after) + + new_sales_order.company = woocommerce_settings.company + + set_items_in_sales_order(new_sales_order, woocommerce_settings, order) + new_sales_order.flags.ignore_mandatory = True + new_sales_order.insert() + new_sales_order.submit() + frappe.db.commit() -def add_tax_details(sales_order,price,desc,status): +def set_items_in_sales_order(new_sales_order, woocommerce_settings, order): + company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr') - woocommerce_settings = frappe.get_doc("Woocommerce Settings") + for item in order.get("line_items"): + woocomm_item_id = item.get("product_id") + found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id}) - if status == 0: - # Product taxes - account_head_type = woocommerce_settings.tax_account + ordered_items_tax = item.get("total_tax") - if status == 1: - # Shipping taxes - account_head_type = woocommerce_settings.f_n_f_account + new_sales_order.append("items",{ + "item_code": found_item.item_code, + "item_name": found_item.item_name, + "description": found_item.item_name, + "delivery_date": new_sales_order.delivery_date, + "uom": woocommerce_settings.uom or _("Nos"), + "qty": item.get("quantity"), + "rate": item.get("price"), + "warehouse": woocommerce_settings.warehouse or _("Stores - {0}").format(company_abbr) + }) - sales_order.append("taxes",{ - "charge_type":"Actual", - "account_head": account_head_type, - "tax_amount": price, - "description": desc - }) + add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account) + + # shipping_details = order.get("shipping_lines") # used for detailed order + + add_tax_details(new_sales_order, order.get("shipping_tax"), "Shipping Tax", woocommerce_settings.f_n_f_account) + add_tax_details(new_sales_order, order.get("shipping_total"), "Shipping Total", woocommerce_settings.f_n_f_account) + +def add_tax_details(sales_order, price, desc, tax_account_head): + sales_order.append("taxes", { + "charge_type":"Actual", + "account_head": tax_account_head, + "tax_amount": price, + "description": desc + }) diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json index dd3c24dce5..956ae09cbd 100644 --- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json +++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.json @@ -1,694 +1,175 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2018-02-12 15:10:05.495713", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "enable_sync", + "sb_00", + "woocommerce_server_url", + "secret", + "cb_00", + "api_consumer_key", + "api_consumer_secret", + "sb_accounting_details", + "tax_account", + "column_break_10", + "f_n_f_account", + "defaults_section", + "creation_user", + "warehouse", + "sales_order_series", + "column_break_14", + "company", + "delivery_after_days", + "uom", + "endpoints", + "endpoint" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "enable_sync", "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": "Enable Sync", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Enable Sync" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sb_00", - "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": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "woocommerce_server_url", "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": "Woocommerce Server URL", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Woocommerce Server URL" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "secret", "fieldtype": "Code", - "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": "Secret", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "cb_00", - "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, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "api_consumer_key", "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": "API consumer key", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "API consumer key" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "api_consumer_secret", "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": "API consumer secret", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "API consumer secret" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sb_accounting_details", "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": "Accounting Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Accounting Details" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "tax_account", "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": "Tax Account", - "length": 0, - "no_copy": 0, "options": "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 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, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "f_n_f_account", "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": "Freight and Forwarding Account", - "length": 0, - "no_copy": 0, "options": "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "defaults_section", "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": "Defaults", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Defaults" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.", - "fetch_if_empty": 0, "fieldname": "creation_user", "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": "Creation User", - "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "This warehouse will be used to create Sale Orders. The fallback warehouse is \"Stores\".", - "fetch_if_empty": 0, + "description": "This warehouse will be used to create Sales Orders. The fallback warehouse is \"Stores\".", "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": 0, "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Warehouse" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 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, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "The fallback series is \"SO-WOO-\".", - "fetch_if_empty": 0, "fieldname": "sales_order_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": "Sales Order Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Sales Order Series" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "This is the default UOM used for items and Sales orders. The fallback UOM is \"Nos\".", - "fetch_if_empty": 0, "fieldname": "uom", "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": "UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "UOM" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "endpoints", "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": "Endpoints", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Endpoints" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "endpoint", "fieldtype": "Code", - "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": "Endpoint", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 + }, + { + "description": "This company will be used to create Sales Orders.", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "description": "This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.", + "fieldname": "delivery_after_days", + "fieldtype": "Int", + "label": "Delivery After (Days)" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, "issingle": 1, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-08 17:04:16.720696", + "modified": "2019-11-04 00:45:21.232096", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Woocommerce Settings", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, - "report": 0, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py index 055684d445..bd072f40a1 100644 --- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py +++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.utils.nestedset import get_root_of from frappe.model.document import Document from six.moves.urllib.parse import urlparse +from frappe.custom.doctype.custom_field.custom_field import create_custom_field class WoocommerceSettings(Document): def validate(self): @@ -17,75 +18,21 @@ class WoocommerceSettings(Document): def create_delete_custom_fields(self): if self.enable_sync: + custom_fields = {} # create - create_custom_field_id_and_check_status = False - create_custom_field_email_check = False - names = ["Customer-woocommerce_id","Sales Order-woocommerce_id","Item-woocommerce_id","Address-woocommerce_id"] - names_check_box = ["Customer-woocommerce_check","Sales Order-woocommerce_check","Item-woocommerce_check","Address-woocommerce_check"] - email_names = ["Customer-woocommerce_email","Address-woocommerce_email"] + for doctype in ["Customer", "Sales Order", "Item", "Address"]: + df = dict(fieldname='woocommerce_id', label='Woocommerce ID', fieldtype='Data', read_only=1, print_hide=1) + create_custom_field(doctype, df) - for i in zip(names,names_check_box): - - if not frappe.get_value("Custom Field",{"name":i[0]}) or not frappe.get_value("Custom Field",{"name":i[1]}): - create_custom_field_id_and_check_status = True - break - - - if create_custom_field_id_and_check_status: - names = ["Customer","Sales Order","Item","Address"] - for name in names: - custom = frappe.new_doc("Custom Field") - custom.dt = name - custom.label = "woocommerce_id" - custom.read_only = 1 - custom.save() - - custom = frappe.new_doc("Custom Field") - custom.dt = name - custom.label = "woocommerce_check" - custom.fieldtype = "Check" - custom.read_only = 1 - custom.save() - - for i in email_names: - - if not frappe.get_value("Custom Field",{"name":i}): - create_custom_field_email_check = True - break; - - if create_custom_field_email_check: - names = ["Customer","Address"] - for name in names: - custom = frappe.new_doc("Custom Field") - custom.dt = name - custom.label = "woocommerce_email" - custom.read_only = 1 - custom.save() - - if not frappe.get_value("Item Group",{"name": _("WooCommerce Products")}): + for doctype in ["Customer", "Address"]: + df = dict(fieldname='woocommerce_email', label='Woocommerce Email', fieldtype='Data', read_only=1, print_hide=1) + create_custom_field(doctype, df) + + if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}): item_group = frappe.new_doc("Item Group") item_group.item_group_name = _("WooCommerce Products") item_group.parent_item_group = get_root_of("Item Group") - item_group.save() - - - elif not self.enable_sync: - # delete - names = ["Customer-woocommerce_id","Sales Order-woocommerce_id","Item-woocommerce_id","Address-woocommerce_id"] - names_check_box = ["Customer-woocommerce_check","Sales Order-woocommerce_check","Item-woocommerce_check","Address-woocommerce_check"] - email_names = ["Customer-woocommerce_email","Address-woocommerce_email"] - for name in names: - frappe.delete_doc("Custom Field",name) - - for name in names_check_box: - frappe.delete_doc("Custom Field",name) - - for name in email_names: - frappe.delete_doc("Custom Field",name) - - frappe.delete_doc("Item Group", _("WooCommerce Products")) - - frappe.db.commit() + item_group.insert() def validate_settings(self): if self.enable_sync: diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index fc850d57d7..ce0f47d685 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -18,6 +18,7 @@ class TestWoocommerce(unittest.TestCase): woo_settings.api_consumer_key = "ck_fd43ff5756a6abafd95fadb6677100ce95a758a1" woo_settings.api_consumer_secret = "cs_94360a1ad7bef7fa420a40cf284f7b3e0788454e" woo_settings.enable_sync = 1 + woo_settings.company = "Woocommerce" woo_settings.tax_account = "Sales Expenses - W" woo_settings.f_n_f_account = "Expenses - W" woo_settings.creation_user = "Administrator" From 015285c42f05440cb9315edea352ca379c630ca5 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 7 Nov 2019 19:38:52 +0530 Subject: [PATCH 072/131] fix: Exception handling in GSTR-1 Report --- erpnext/regional/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 922619cc42..090616b077 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -116,7 +116,7 @@ class Gstr1Report(object): taxable_value = 0 for item_code, net_amount in self.invoice_items.get(invoice).items(): if item_code in items: - if self.item_tax_rate.get(invoice) and tax_rate in self.item_tax_rate.get(invoice, {}).get(item_code): + if self.item_tax_rate.get(invoice) and tax_rate in self.item_tax_rate.get(invoice, {}).get(item_code, []): taxable_value += abs(net_amount) elif not self.item_tax_rate.get(invoice): taxable_value += abs(net_amount) From 62fbf37eb46a50464549e2ad871d9be22b7642b2 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 7 Nov 2019 21:54:25 +0530 Subject: [PATCH 073/131] fix: GSTIN validation msg fix --- erpnext/regional/india/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0bc277f4af..aae07797a1 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -72,8 +72,8 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - frappe.throw(_("Invalid {0}! The check digit validation has failed. " + - "Please ensure you've typed the {0} correctly.".format(label))) + frappe.throw(_("""Invalid {0}! The check digit validation has failed. + Please ensure you've typed the {0} correctly.""".format(label))) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): From 7aef9f3b43cf64a5b214ac4f5f0accb88b354e46 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 8 Nov 2019 12:42:38 +0530 Subject: [PATCH 074/131] fix: Accounting Dimension custom fields should be admin owned (#19525) * fix: Accounting Dimension custom fileds should be admin owned * fix: Update query * fix: Travis --- .../accounting_dimension.py | 8 +++++++- erpnext/patches.txt | 1 + ...ner_fields_in_acc_dimension_custom_fields.py | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index af51fc5d8e..522ed4ffa4 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -24,6 +24,11 @@ class AccountingDimension(Document): msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) frappe.throw(msg) + exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name']) + + if exists and self.is_new(): + frappe.throw("Document Type already used as a dimension") + def after_insert(self): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) @@ -60,7 +65,8 @@ def make_dimension_in_accounting_doctypes(doc): "label": doc.label, "fieldtype": "Link", "options": doc.document_type, - "insert_after": insert_after_field + "insert_after": insert_after_field, + "owner": "Administrator" } if doctype == "Budget": diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cc00b7e412..f594f96aea 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -642,3 +642,4 @@ erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status +erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields diff --git a/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py new file mode 100644 index 0000000000..e4dcecd9bd --- /dev/null +++ b/erpnext/patches/v12_0/update_owner_fields_in_acc_dimension_custom_fields.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals +import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_doctypes_with_dimensions + +def execute(): + accounting_dimensions = frappe.db.sql("""select fieldname from + `tabAccounting Dimension`""", as_dict=1) + + doclist = get_doctypes_with_dimensions() + + for dimension in accounting_dimensions: + frappe.db.sql(""" + UPDATE `tabCustom Field` + SET owner = 'Administrator' + WHERE fieldname = %s + AND dt IN (%s)""" % #nosec + ('%s', ', '.join(['%s']* len(doclist))), tuple([dimension.fieldname] + doclist)) \ No newline at end of file From 7a23057eabc8b552c1e666ca4314e6fb086bde0f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 8 Nov 2019 12:52:54 +0530 Subject: [PATCH 075/131] feat: Enhancement in landed cost voucher (#19252) * feat: Enhancement in landed cost voucher * fix: Make GL entries based on ledgers in Landed cost voucher * fix: Patch to update expense account in Landed Cost Voucher and Stock Entry * fix: Ability to select expense account in Stock Entry * fix: Renaming and test case fixes * fix: Test Cases * fix: Additional cost in Stock Entry * fix: Changed filters and test case fixes * fix: Upadte filters in stokc entry expense account filter * fix: company filter --- .../purchase_invoice/purchase_invoice.py | 22 ++- erpnext/manufacturing/doctype/bom/bom.py | 3 + .../doctype/work_order/work_order.py | 3 +- erpnext/patches.txt | 1 + ...se_account_in_landed_cost_voucher_taxes.py | 33 ++++ .../landed_cost_taxes_and_charges.json | 164 +++++------------- .../landed_cost_voucher.js | 16 +- .../test_landed_cost_voucher.py | 2 +- .../purchase_receipt/purchase_receipt.py | 47 ++++- .../stock/doctype/stock_entry/stock_entry.js | 15 +- .../stock/doctype/stock_entry/stock_entry.py | 57 +++--- .../doctype/stock_entry/test_stock_entry.py | 8 +- 12 files changed, 202 insertions(+), 169 deletions(-) create mode 100644 erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4ea9b1c6c9..9c1a9ece77 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -25,6 +25,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_ unlink_inter_company_doc from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.accounts.deferred_revenue import validate_service_stop_date +from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -436,6 +437,8 @@ class PurchaseInvoice(BuyingController): if self.update_stock and self.auto_accounting_for_stock: warehouse_account = get_warehouse_account_map(self.company) + landed_cost_entries = get_item_account_wise_additional_cost(self.name) + voucher_wise_stock_value = {} if self.update_stock: for d in frappe.get_all('Stock Ledger Entry', @@ -463,15 +466,16 @@ class PurchaseInvoice(BuyingController): ) # Amount added through landed-cost-voucher - if flt(item.landed_cost_voucher_amount): - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_valuation, - "against": item.expense_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project - }, item=item)) + if landed_cost_entries: + for account, amount in iteritems(landed_cost_entries[(item.item_code, item.name)]): + gl_entries.append(self.get_gl_dict({ + "account": account, + "against": item.expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(amount), + "project": item.project + }, item=item)) # sub-contracting warehouse if flt(item.rm_supp_cost): diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index a1bf35fd08..db79d7feda 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -776,6 +776,8 @@ def add_additional_cost(stock_entry, work_order): # Add non stock items cost in the additional cost bom = frappe.get_doc('BOM', work_order.bom_no) table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items' + expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company, + "expenses_included_in_valuation") items = {} for d in bom.get(table): @@ -786,6 +788,7 @@ def add_additional_cost(stock_entry, work_order): for name in non_stock_items: stock_entry.append('additional_costs', { + 'expense_account': expenses_included_in_valuation, 'description': name[0], 'amount': items.get(name[0]) }) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index b57548e960..ae4d9be282 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -645,7 +645,8 @@ def make_stock_entry(work_order_id, purpose, qty=None): stock_entry.to_warehouse = work_order.fg_warehouse stock_entry.project = work_order.project if purpose=="Manufacture": - additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty) + additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty, + company=work_order.company) stock_entry.set("additional_costs", additional_costs) stock_entry.set_stock_entry_type() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f594f96aea..047fc85f0a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -640,6 +640,7 @@ erpnext.patches.v12_0.create_default_energy_point_rules erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type +erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields diff --git a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py new file mode 100644 index 0000000000..a996a69b3d --- /dev/null +++ b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py @@ -0,0 +1,33 @@ +from __future__ import unicode_literals +import frappe +from six import iteritems + +def execute(): + frappe.reload_doctype('Landed Cost Taxes and Charges') + + company_account_map = frappe._dict(frappe.db.sql(""" + SELECT name, expenses_included_in_valuation from `tabCompany` + """)) + + for company, account in iteritems(company_account_map): + frappe.db.sql(""" + UPDATE + `tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l + SET + t.expense_account = %s + WHERE + l.docstatus = 1 + AND l.company = %s + AND t.parent = l.name + """, (account, company)) + + frappe.db.sql(""" + UPDATE + `tabLanded Cost Taxes and Charges` t, `tabStock Entry` s + SET + t.expense_account = %s + WHERE + s.docstatus = 1 + AND s.company = %s + AND t.parent = s.name + """, (account, company)) \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 1aaf73f3ad..0cc243d4cb 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -1,129 +1,51 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-07-11 11:51:00.453717", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2014-07-11 11:51:00.453717", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "expense_account", + "description", + "col_break3", + "amount" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "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": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "col_break3", + "fieldtype": "Column Break", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": "Amount", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Expense Account", + "options": "Account", + "reqd": 1 } - ], - "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-11-15 19:27:59.542487", - "modified_by": "Administrator", - "module": "Stock", - "name": "Landed Cost Taxes and Charges", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "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 + ], + "istable": 1, + "modified": "2019-09-30 18:28:32.070655", + "modified_by": "Administrator", + "module": "Stock", + "name": "Landed Cost Taxes and Charges", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index edc3444220..c9a3fd976f 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -30,6 +30,16 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ this.frm.add_fetch("receipt_document", "posting_date", "posting_date"); this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total"); + this.frm.set_query("expense_account", "taxes", function() { + return { + query: "erpnext.controllers.queries.tax_account_query", + filters: { + "account_type": ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"], + "company": me.frm.doc.company + } + }; + }); + }, refresh: function(frm) { @@ -38,7 +48,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({

    - + ${__("Notes")}:

      @@ -96,7 +106,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ var me = this; if(this.frm.doc.taxes.length) { - + var total_item_cost = 0.0; var based_on = this.frm.doc.distribute_charges_based_on.toLowerCase(); $.each(this.frm.doc.items || [], function(i, d) { @@ -105,7 +115,7 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ var total_charges = 0.0; $.each(this.frm.doc.items || [], function(i, item) { - item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost) + item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost) item.applicable_charges = flt(item.applicable_charges, precision("applicable_charges", item)) total_charges += item.applicable_charges }); diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 4dc0b7b059..fe5d3ed6df 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -179,7 +179,7 @@ def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges= lcv.set("taxes", [{ "description": "Insurance Charges", - "account": "_Test Account Insurance Charges - _TC", + "expense_account": "Expenses Included In Valuation - TCP1", "amount": charges }]) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index fae4de3691..3362d4b0f5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -195,6 +195,7 @@ class PurchaseReceipt(BuyingController): from erpnext.accounts.general_ledger import process_gl_map stock_rbnb = self.get_company_default("stock_received_but_not_billed") + landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") gl_entries = [] @@ -233,15 +234,16 @@ class PurchaseReceipt(BuyingController): negative_expense_to_be_booked += flt(d.item_tax_amount) # Amount added through landed-cost-voucher - if flt(d.landed_cost_voucher_amount): - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_valuation, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(d.landed_cost_voucher_amount), - "project": d.project - }, item=d)) + if landed_cost_entries: + for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]): + gl_entries.append(self.get_gl_dict({ + "account": account, + "against": warehouse_account[d.warehouse]["account"], + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(amount), + "project": d.project + }, item=d)) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): @@ -584,3 +586,30 @@ def make_stock_entry(source_name,target_doc=None): }, target_doc, set_missing_values) return doclist + +def get_item_account_wise_additional_cost(purchase_document): + landed_cost_voucher = frappe.get_value("Landed Cost Purchase Receipt", + {"receipt_document": purchase_document}, "parent") + + if not landed_cost_voucher: + return + + total_item_cost = 0 + item_account_wise_cost = {} + landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", landed_cost_voucher) + based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) + + for item in landed_cost_voucher_doc.items: + if item.receipt_document == purchase_document: + total_item_cost += item.get(based_on_field) + + for item in landed_cost_voucher_doc.items: + if item.receipt_document == purchase_document: + for account in landed_cost_voucher_doc.taxes: + item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {}) + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(account.expense_account, 0.0) + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account] += \ + account.amount * item.get(based_on_field) / total_item_cost + + return item_account_wise_cost + diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 0b023024f9..6e78b988f6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -68,6 +68,16 @@ frappe.ui.form.on('Stock Entry', { } }); + frm.set_query("expense_account", "additional_costs", function() { + return { + query: "erpnext.controllers.queries.tax_account_query", + filters: { + "account_type": ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"], + "company": frm.doc.company + } + }; + }); + frm.add_fetch("bom_no", "inspection_required", "inspection_required"); }, @@ -727,7 +737,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ return frappe.call({ method: "erpnext.stock.doctype.stock_entry.stock_entry.get_work_order_details", args: { - work_order: me.frm.doc.work_order + work_order: me.frm.doc.work_order, + company: me.frm.doc.company }, callback: function(r) { if (!r.exc) { @@ -743,6 +754,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ if (me.frm.doc.purpose == "Manufacture") { if (!me.frm.doc.to_warehouse) me.frm.set_value("to_warehouse", r.message["fg_warehouse"]); if (r.message["additional_costs"].length) { + me.frm.clear_table("additional_costs"); + $.each(r.message["additional_costs"], function(i, row) { me.frm.add_child("additional_costs", row); }) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 55e02a46ff..26693d208b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -644,28 +644,37 @@ class StockEntry(StockController): self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No') def get_gl_entries(self, warehouse_account): - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) - for d in self.get("items"): - additional_cost = flt(d.additional_cost, d.precision("additional_cost")) - if additional_cost: - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_valuation, - "against": d.expense_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": additional_cost - }, item=d)) + total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + item_account_wise_additional_cost = {} - gl_entries.append(self.get_gl_dict({ - "account": d.expense_account, - "against": expenses_included_in_valuation, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": -1 * additional_cost # put it as negative credit instead of debit purposefully - }, item=d)) + for t in self.get("additional_costs"): + for d in self.get("items"): + if d.t_warehouse: + item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) + item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0) + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ + (t.amount * d.basic_amount) / total_basic_amount + + if item_account_wise_additional_cost: + for d in self.get("items"): + for account, amount in iteritems(item_account_wise_additional_cost.get((d.item_code, d.name), {})): + gl_entries.append(self.get_gl_dict({ + "account": account, + "against": d.expense_account, + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": amount + }, item=d)) + + gl_entries.append(self.get_gl_dict({ + "account": d.expense_account, + "against": account, + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": -1 * amount # put it as negative credit instead of debit purposefully + }, item=d)) return gl_entries @@ -1349,7 +1358,7 @@ def make_stock_in_entry(source_name, target_doc=None): return doclist @frappe.whitelist() -def get_work_order_details(work_order): +def get_work_order_details(work_order, company): work_order = frappe.get_doc("Work Order", work_order) pending_qty_to_produce = flt(work_order.qty) - flt(work_order.produced_qty) @@ -1360,14 +1369,17 @@ def get_work_order_details(work_order): "wip_warehouse": work_order.wip_warehouse, "fg_warehouse": work_order.fg_warehouse, "fg_completed_qty": pending_qty_to_produce, - "additional_costs": get_additional_costs(work_order, fg_qty=pending_qty_to_produce) + "additional_costs": get_additional_costs(work_order, fg_qty=pending_qty_to_produce, company=company) } -def get_additional_costs(work_order=None, bom_no=None, fg_qty=None): +def get_additional_costs(work_order=None, bom_no=None, fg_qty=None, company=None): additional_costs = [] operating_cost_per_unit = get_operating_cost_per_unit(work_order, bom_no) + expenses_included_in_valuation = frappe.get_cached_value("Company", company, "expenses_included_in_valuation") + if operating_cost_per_unit: additional_costs.append({ + "expense_account": expenses_included_in_valuation, "description": "Operating Cost as per Work Order / BOM", "amount": operating_cost_per_unit * flt(fg_qty) }) @@ -1377,6 +1389,7 @@ def get_additional_costs(work_order=None, bom_no=None, fg_qty=None): flt(work_order.additional_operating_cost) / flt(work_order.qty) additional_costs.append({ + "expense_account": expenses_included_in_valuation, "description": "Additional Operating Cost", "amount": additional_operating_cost_per_unit * flt(fg_qty) }) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 941472bbf9..eddab5d79d 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -259,6 +259,8 @@ class TestStockEntry(unittest.TestCase): repack.posting_date = nowdate() repack.posting_time = nowtime() + expenses_included_in_valuation = frappe.get_value("Company", company, "expenses_included_in_valuation") + items = get_multiple_items() repack.items = [] for item in items: @@ -266,11 +268,13 @@ class TestStockEntry(unittest.TestCase): repack.set("additional_costs", [ { - "description": "Actual Oerating Cost", + "expense_account": expenses_included_in_valuation, + "description": "Actual Operating Cost", "amount": 1000 }, { - "description": "additional operating costs", + "expense_account": expenses_included_in_valuation, + "description": "Additional Operating Cost", "amount": 200 }, ]) From e93dc9f1cdd9998504dbd711cc60c6ac5bbe68d7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 Nov 2019 13:43:15 +0530 Subject: [PATCH 076/131] fix: precision issue --- erpnext/controllers/accounts_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 67f453d2b3..9415228467 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1193,8 +1193,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil frappe.throw(_("Cannot set quantity less than received quantity")) child_item.qty = flt(d.get("qty")) + precision = child_item.precision("rate") or 2 - if flt(child_item.billed_amt) > (flt(d.get("rate")) * flt(d.get("qty"))): + if flt(child_item.billed_amt, precision) > flt(flt(d.get("rate")) * flt(d.get("qty")), precision): frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.") .format(child_item.idx, child_item.item_code)) else: From d36c6068b281f73110117bb5965f436cb4ae1569 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 8 Nov 2019 14:43:53 +0530 Subject: [PATCH 077/131] fix: 'NoneType' object has no attribute 'stock_uom' (#19532) --- erpnext/stock/doctype/pick_list/pick_list.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 3f66743f07..278971125f 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -173,8 +173,10 @@ frappe.ui.form.on('Pick List Item', { }); function get_item_details(item_code, uom=null) { - return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', { - item_code, - uom - }); + if (item_code) { + return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', { + item_code, + uom + }); + } } \ No newline at end of file From b81942404d96541c9d82226f4866318f22460dc0 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 8 Nov 2019 14:50:48 +0530 Subject: [PATCH 078/131] fix: incorrect balance qty in stock ledger if batch filter set (#19479) --- .../stock/report/stock_ledger/stock_ledger.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 5bdbca23d9..db7f6ad1b9 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -19,10 +19,26 @@ def execute(filters=None): if opening_row: data.append(opening_row) + actual_qty = stock_value = 0 + for sle in sl_entries: item_detail = item_details[sle.item_code] sle.update(item_detail) + + if filters.get("batch_no"): + actual_qty += sle.actual_qty + stock_value += sle.stock_value_difference + + if sle.voucher_type == 'Stock Reconciliation': + actual_qty = sle.qty_after_transaction + stock_value = sle.stock_value + + sle.update({ + "qty_after_transaction": actual_qty, + "stock_value": stock_value + }) + data.append(sle) if include_uom: @@ -67,7 +83,7 @@ def get_stock_ledger_entries(filters, items): return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date, item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate, - stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project + stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project, stock_value_difference from `tabStock Ledger Entry` sle where company = %(company)s and posting_date between %(from_date)s and %(to_date)s From 2f44480d69c7b43eb6d5f7d4d1cd94efbe040acb Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 11 Nov 2019 10:54:12 +0530 Subject: [PATCH 079/131] enhancement: Item-wise sales history report (#19500) * refactor(item-wise-sales-history): convert query report to script report * refactor: add columns, fetch data * refactor: shift company set func to utils * fix: add filters * fix: minor changes * fix: fetch all the descendants --- .../hr/doctype/staffing_plan/staffing_plan.py | 16 +- .../item_wise_sales_history.js | 33 +++ .../item_wise_sales_history.json | 42 ++-- .../item_wise_sales_history.py | 214 ++++++++++++++++++ 4 files changed, 272 insertions(+), 33 deletions(-) create mode 100644 erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js create mode 100644 erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index e6afbcc220..595bcaa8d4 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -7,6 +7,7 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import getdate, nowdate, cint, flt +from frappe.utils.nestedset import get_descendants_of class SubsidiaryCompanyError(frappe.ValidationError): pass class ParentCompanyError(frappe.ValidationError): pass @@ -131,7 +132,8 @@ def get_designation_counts(designation, company): return False employee_counts = {} - company_set = get_company_set(company) + company_set = get_descendants_of('Company', company) + company_set.append(company) employee_counts["employee_count"] = frappe.db.get_value("Employee", filters={ @@ -167,14 +169,4 @@ def get_active_staffing_plan_details(company, designation, from_date=getdate(now designation, from_date, to_date) # Only a single staffing plan can be active for a designation on given date - return staffing_plan if staffing_plan else None - -def get_company_set(company): - return frappe.db.sql_list(""" - SELECT - name - FROM `tabCompany` - WHERE - parent_company=%(company)s - OR name=%(company)s - """, (dict(company=company))) \ No newline at end of file + return staffing_plan if staffing_plan else None \ No newline at end of file diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js new file mode 100644 index 0000000000..ee806a78fb --- /dev/null +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js @@ -0,0 +1,33 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Item-wise Sales History"] = { + "filters": [ + { + fieldname:"company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + fieldname:"item_group", + label: __("Item Group"), + fieldtype: "Link", + options: "Item Group" + }, + { + fieldname:"from_date", + label: __("From Date"), + fieldtype: "Date", + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + }, + + ] +}; \ No newline at end of file diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json index 88e6f27395..a6dda289de 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json @@ -1,34 +1,34 @@ { - "add_total_row": 1, - "creation": "2013-05-23 17:42:24", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2019-01-03 22:52:41.519890", - "modified_by": "Administrator", - "module": "Selling", - "name": "Item-wise Sales History", - "owner": "Administrator", - "prepared_report": 0, - "query": "select\n so_item.item_code as \"Item Code:Link/Item:120\",\n\tso_item.item_name as \"Item Name::120\",\n so_item.item_group as \"Item Group:Link/Item Group:120\",\n\tso_item.description as \"Description::150\",\n\tso_item.qty as \"Qty:Data:100\",\n\tso_item.uom as \"UOM:Link/UOM:80\",\n\tso_item.base_rate as \"Rate:Currency:120\",\n\tso_item.base_amount as \"Amount:Currency:120\",\n\tso.name as \"Sales Order:Link/Sales Order:120\",\n\tso.transaction_date as \"Transaction Date:Date:140\",\n\tso.customer as \"Customer:Link/Customer:130\",\n cu.customer_name as \"Customer Name::150\",\n\tcu.customer_group as \"Customer Group:Link/Customer Group:130\",\n\tso.territory as \"Territory:Link/Territory:130\",\n \tso.project as \"Project:Link/Project:130\",\n\tifnull(so_item.delivered_qty, 0) as \"Delivered Qty:Float:120\",\n\tifnull(so_item.billed_amt, 0) as \"Billed Amount:Currency:120\",\n\tso.company as \"Company:Link/Company:\"\nfrom\n\t`tabSales Order` so, `tabSales Order Item` so_item, `tabCustomer` cu\nwhere\n\tso.name = so_item.parent and so.customer=cu.name\n\tand so.docstatus = 1\norder by so.name desc", - "ref_doctype": "Sales Order", - "report_name": "Item-wise Sales History", - "report_type": "Query Report", + "add_total_row": 1, + "creation": "2013-05-23 17:42:24", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 3, + "is_standard": "Yes", + "modified": "2019-11-04 16:28:14.608904", + "modified_by": "Administrator", + "module": "Selling", + "name": "Item-wise Sales History", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Order", + "report_name": "Item-wise Sales History", + "report_type": "Script Report", "roles": [ { "role": "Sales User" - }, + }, { "role": "Sales Manager" - }, + }, { "role": "Maintenance User" - }, + }, { "role": "Accounts User" - }, + }, { "role": "Stock User" } diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py new file mode 100644 index 0000000000..226c34f735 --- /dev/null +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -0,0 +1,214 @@ +# Copyright (c) 2019, 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 +from frappe.utils.nestedset import get_descendants_of + +def execute(filters=None): + filters = frappe._dict(filters or {}) + columns = get_columns(filters) + data = get_data(filters) + return columns, data + +def get_columns(filters): + return [ + { + "label": _("Item Code"), + "fieldtype": "Link", + "fieldname": "item_code", + "options": "Item", + "width": 120 + }, + { + "label": _("Item Name"), + "fieldtype": "Data", + "fieldname": "item_name", + "width": 140 + }, + { + "label": _("Item Group"), + "fieldtype": "Link", + "fieldname": "item_group", + "options": "Item Group", + "width": 120 + }, + { + "label": _("Description"), + "fieldtype": "Data", + "fieldname": "description", + "width": 150 + }, + { + "label": _("Quantity"), + "fieldtype": "Float", + "fieldname": "quantity", + "width": 150 + }, + { + "label": _("UOM"), + "fieldtype": "Link", + "fieldname": "uom", + "options": "UOM", + "width": 100 + }, + { + "label": _("Rate"), + "fieldname": "rate", + "options": "Currency", + "width": 120 + }, + { + "label": _("Amount"), + "fieldname": "amount", + "options": "Currency", + "width": 120 + }, + { + "label": _("Sales Order"), + "fieldtype": "Link", + "fieldname": "sales_order", + "options": "Sales Order", + "width": 100 + }, + { + "label": _("Transaction Date"), + "fieldtype": "Date", + "fieldname": "transaction_date", + "width": 90 + }, + { + "label": _("Customer"), + "fieldtype": "Link", + "fieldname": "customer", + "options": "Customer", + "width": 100 + }, + { + "label": _("Customer Name"), + "fieldtype": "Data", + "fieldname": "customer_name", + "width": 140 + }, + { + "label": _("Customer Group"), + "fieldtype": "Link", + "fieldname": "customer_group", + "options": "customer Group", + "width": 120 + }, + { + "label": _("Territory"), + "fieldtype": "Link", + "fieldname": "territory", + "options": "Territory", + "width": 100 + }, + { + "label": _("Project"), + "fieldtype": "Link", + "fieldname": "project", + "options": "Project", + "width": 100 + }, + { + "label": _("Delivered Quantity"), + "fieldtype": "Float", + "fieldname": "delivered_quantity", + "width": 150 + }, + { + "label": _("Billed Amount"), + "fieldname": "rate", + "options": "billed_amount", + "width": 120 + }, + { + "label": _("Company"), + "fieldtype": "Link", + "fieldname": "company", + "options": "Company", + "width": 100 + } + ] + +def get_data(filters): + + data = [] + + company_list = get_descendants_of("Company", filters.get("company")) + company_list.append(filters.get("company")) + + customer_details = get_customer_details() + sales_order_records = get_sales_order_details(company_list, filters) + + for record in sales_order_records: + customer_record = customer_details.get(record.customer) + row = { + "item_code": record.item_code, + "item_name": record.item_name, + "item_group": record.item_group, + "description": record.description, + "quantity": record.qty, + "uom": record.uom, + "rate": record.base_rate, + "amount": record.base_amount, + "sales_order": record.name, + "transaction_date": record.transaction_date, + "customer": record.customer, + "customer_name": customer_record.customer_name, + "customer_group": customer_record.customer_group, + "territory": record.territory, + "project": record.project, + "delivered_quantity": flt(record.delivered_qty), + "billed_amount": flt(record.billed_amt), + "company": record.company + } + data.append(row) + + return data + +def get_conditions(filters): + conditions = '' + if filters.get('item_group'): + conditions += "AND so_item.item_group = %s" %frappe.db.escape(filters.item_group) + + if filters.get('from_date'): + conditions += "AND so.transaction_date >= '%s'" %filters.from_date + + if filters.get('to_date'): + conditions += "AND so.transaction_date <= '%s'" %filters.to_date + + return conditions + +def get_customer_details(): + details = frappe.get_all('Customer', + fields=['name', 'customer_name', "customer_group"]) + customer_details = {} + for d in details: + customer_details.setdefault(d.name, frappe._dict({ + "customer_name": d.customer_name, + "customer_group": d.customer_group + })) + return customer_details + +def get_sales_order_details(company_list, filters): + conditions = get_conditions(filters) + return frappe.db.sql(""" + SELECT + so_item.item_code, so_item.item_name, so_item.item_group, + so_item.description, so_item.qty, so_item.uom, + so_item.base_rate, so_item.base_amount, so.name, + so.transaction_date, so.customer, so.territory, + so.project, so_item.delivered_qty, + so_item.billed_amt, so.company + FROM + `tabSales Order` so, `tabSales Order Item` so_item + WHERE + so.name = so_item.parent + AND so.company in (%s) + AND so.docstatus = 1 + {0} + """.format(conditions), company_list, as_dict=1) #nosec From df75b7693aad8a8b3505d71c561d6ebdef0eaffe Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 11 Nov 2019 10:59:10 +0530 Subject: [PATCH 080/131] fix: added description, uom fields in material request plan item table (#19463) --- .../material_request_plan_item.json | 546 ++++-------------- .../production_plan/production_plan.js | 4 +- .../production_plan/production_plan.py | 5 +- 3 files changed, 125 insertions(+), 430 deletions(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 39d59f006b..f27197d09f 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -1,443 +1,135 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-01 12:12:55.048691", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2017-12-01 12:12:55.048691", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "warehouse", + "material_request_type", + "column_break_4", + "quantity", + "uom", + "projected_qty", + "actual_qty", + "item_details", + "description", + "min_order_qty", + "section_break_8", + "sales_order", + "requested_qty" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 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": 1, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "item_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": "Item Name", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 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": 1, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Warehouse", + "options": "Warehouse", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "material_request_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": "Material Request Type", - "length": 0, - "no_copy": 0, - "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "material_request_type", + "fieldtype": "Select", + "label": "Material Request Type", + "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "quantity", - "fieldtype": "Float", - "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": "Required Quantity", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "quantity", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Quantity", + "no_copy": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "projected_qty", - "fieldtype": "Float", - "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": "Projected Qty", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "projected_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Projected Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "actual_qty", - "fieldtype": "Float", - "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": "Actual Qty", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "actual_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Actual Qty", + "no_copy": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "min_order_qty", - "fieldtype": "Float", - "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": "Minimum Order Quantity", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "min_order_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Minimum Order Quantity", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 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": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Reference" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_order", - "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": "Sales Order", - "length": 0, - "no_copy": 0, - "options": "Sales 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "options": "Sales Order", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "requested_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Requested Qty", - "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, - "translatable": 0, - "unique": 0 + "fieldname": "requested_qty", + "fieldtype": "Float", + "label": "Requested Qty", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "item_details", + "fieldtype": "Section Break", + "label": "Item Description" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-04-08 18:15:26.849602", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Material Request Plan Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "modified": "2019-11-08 15:15:43.979360", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Material Request Plan Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 96bb0ae37f..51989378d8 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -182,8 +182,8 @@ frappe.ui.form.on('Production Plan', { }, get_items_for_mr: function(frm) { - const set_fields = ['actual_qty', 'item_code', - 'item_name', 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; + const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', + 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests", freeze: true, diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 10d9a474e0..0f49f73dfb 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -528,6 +528,7 @@ def get_material_request_items(row, sales_order, required_qty = ceil(required_qty) if required_qty > 0: + print(row) return { 'item_code': row.item_code, 'item_name': row.item_name, @@ -540,7 +541,9 @@ def get_material_request_items(row, sales_order, 'projected_qty': bin_dict.get("projected_qty", 0), 'min_order_qty': row['min_order_qty'], 'material_request_type': row.get("default_material_request_type"), - 'sales_order': sales_order + 'sales_order': sales_order, + 'description': row.get("description"), + 'uom': row.get("purchase_uom") or row.get("stock_uom") } def get_sales_orders(self): From 8bd2d4d35e9f4f4eeeac56b466ae5333e5884184 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 11 Nov 2019 10:59:20 +0530 Subject: [PATCH 081/131] fix(trial balance): Show opening and closing of group account in single column (#19509) --- .../report/trial_balance/trial_balance.py | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 10e977acbf..faeee0f76a 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -76,8 +76,7 @@ def get_data(filters): accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency) - data = filter_out_zero_value_rows(data, parent_children_map, - show_zero_values=filters.get("show_zero_values")) + data = filter_out_zero_value_rows(data, parent_children_map, show_zero_values=filters.get("show_zero_values")) return data @@ -187,33 +186,11 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, d["closing_debit"] = d["opening_debit"] + d["debit"] d["closing_credit"] = d["opening_credit"] + d["credit"] - total_row["debit"] += d["debit"] - total_row["credit"] += d["credit"] - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["opening_debit"] -= d["opening_credit"] - d["closing_debit"] -= d["closing_credit"] + prepare_opening_closing(d) - # For opening - check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit") - - # For closing - check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit") - - if d["root_type"] == "Liability" or d["root_type"] == "Income": - d["opening_credit"] -= d["opening_debit"] - d["closing_credit"] -= d["closing_debit"] - - # For opening - check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit") - - # For closing - check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit") - - total_row["opening_debit"] += d["opening_debit"] - total_row["closing_debit"] += d["closing_debit"] - total_row["opening_credit"] += d["opening_credit"] - total_row["closing_credit"] += d["closing_credit"] + for field in value_fields: + total_row[field] += d[field] return total_row @@ -227,6 +204,10 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr data = [] for d in accounts: + # Prepare opening closing for group account + if parent_children_map.get(d.account): + prepare_opening_closing(d) + has_value = False row = { "account": d.name, @@ -313,11 +294,16 @@ def get_columns(): } ] -def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column): - # If opening debit has negetive value then move it to opening credit and vice versa. +def prepare_opening_closing(row): + dr_or_cr = "debit" if row["root_type"] in ["Asset", "Equity", "Expense"] else "credit" + reverse_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" - if d[dr_or_cr] < 0: - d[switch_to_column] = abs(d[dr_or_cr]) - d[dr_or_cr] = 0.0 - else: - d[switch_to_column] = 0.0 + for col_type in ["opening", "closing"]: + valid_col = col_type + "_" + dr_or_cr + reverse_col = col_type + "_" + reverse_dr_or_cr + row[valid_col] -= row[reverse_col] + if row[valid_col] < 0: + row[reverse_col] = abs(row[valid_col]) + row[valid_col] = 0.0 + else: + row[reverse_col] = 0.0 \ No newline at end of file From 06c6f7cfd3862955229d95168b77b39c5cd56916 Mon Sep 17 00:00:00 2001 From: Tufan Kaynak <31142607+toofun666@users.noreply.github.com> Date: Mon, 11 Nov 2019 14:41:16 +0300 Subject: [PATCH 082/131] fix: Currency Exchange for_selling and for_buying on the same day (#19339) * fix: test data of Currency Exchange to incluse buying and selling test data of Currency Exchange to incluse buying and selling * fix: Currency Exchange Test corrected to include selling and buying exchange_rate Currency Exchange Test corrected to include selling and buying exchange_rate * fix: Currency Exchange for_selling and for_buying fields test and functionality restored In this fix: * You can now add a separate exchange_rate in date for_selling and for_buying. You could not before because the unique field name was only calculated to allow a single name for a date * tests did not account for for_selling and for_buying fields and thier uniqueness * Update test_currency_exchange.py * Update test_records.json * fix: update test_records.json * Update test_records.json * The basic package for turkey is defined. It is empty now but applications specific to Turkey will be created under that package. * fix: update code with scapes vs.tabs updated the code regarding spaces issue * Update currency_exchange.py --- erpnext/regional/turkey/__init__.py | 0 .../currency_exchange/currency_exchange.py | 11 ++- .../test_currency_exchange.py | 38 +++++--- .../currency_exchange/test_records.json | 96 +++++++++++-------- 4 files changed, 85 insertions(+), 60 deletions(-) create mode 100644 erpnext/regional/turkey/__init__.py diff --git a/erpnext/regional/turkey/__init__.py b/erpnext/regional/turkey/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py index 4effb5ab01..60d367a4bb 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py @@ -11,10 +11,15 @@ from frappe.utils import get_datetime_str, formatdate, nowdate, cint class CurrencyExchange(Document): def autoname(self): + purpose = "" if not self.date: self.date = nowdate() - self.name = '{0}-{1}-{2}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"), - self.from_currency, self.to_currency) + if cint(self.for_buying)==0 and cint(self.for_selling)==1: + purpose = "Selling" + if cint(self.for_buying)==1 and cint(self.for_selling)==0: + purpose = "Buying" + self.name = '{0}-{1}-{2}{3}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"), + self.from_currency, self.to_currency, ("-" + purpose) if purpose else "") def validate(self): self.validate_value("exchange_rate", ">", 0) @@ -23,4 +28,4 @@ class CurrencyExchange(Document): throw(_("From Currency and To Currency cannot be same")) if not cint(self.for_buying) and not cint(self.for_selling): - throw(_("Currency Exchange must be applicable for Buying or for Selling.")) \ No newline at end of file + throw(_("Currency Exchange must be applicable for Buying or for Selling.")) diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index c488b996ff..857f666b2a 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -4,15 +4,21 @@ from __future__ import unicode_literals import frappe, unittest from frappe.utils import flt from erpnext.setup.utils import get_exchange_rate +from frappe.utils import cint test_records = frappe.get_test_records('Currency Exchange') def save_new_records(test_records): for record in test_records: + purpose = str("") + if cint(record.get("for_buying"))==0 and cint(record.get("for_selling"))==1: + purpose = "Selling" + if cint(record.get("for_buying"))==1 and cint(record.get("for_selling"))==0: + purpose = "Buying" kwargs = dict( doctype=record.get("doctype"), - docname=record.get("date") + '-' + record.get("from_currency") + '-' + record.get("to_currency"), + docname=record.get("date") + '-' + record.get("from_currency") + '-' + record.get("to_currency") + '-' + purpose, fieldname="exchange_rate", value=record.get("exchange_rate"), ) @@ -25,6 +31,8 @@ def save_new_records(test_records): curr_exchange.from_currency = record["from_currency"] curr_exchange.to_currency = record["to_currency"] curr_exchange.exchange_rate = record["exchange_rate"] + curr_exchange.for_buying = record["for_buying"] + curr_exchange.for_selling = record["for_selling"] curr_exchange.insert() @@ -44,18 +52,18 @@ class TestCurrencyExchange(unittest.TestCase): frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) # Start with allow_stale is True - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") self.assertEqual(flt(exchange_rate, 3), 60.0) - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") self.assertEqual(exchange_rate, 65.1) - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io self.clear_cache() - exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15") + exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling") self.assertFalse(exchange_rate == 60) self.assertEqual(flt(exchange_rate, 3), 66.894) @@ -64,35 +72,35 @@ class TestCurrencyExchange(unittest.TestCase): frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) frappe.db.set_value("Accounts Settings", None, "stale_days", 1) - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") self.assertEqual(exchange_rate, 60.0) # Will fetch from fixer.io self.clear_cache() - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") self.assertEqual(flt(exchange_rate, 3), 67.79) - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io self.clear_cache() - exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15") + exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying") self.assertEqual(flt(exchange_rate, 3), 66.894) - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10") + exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling") self.assertEqual(exchange_rate, 65.1) # NGN is not available on fixer.io so these should return 0 - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09") + exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling") self.assertEqual(exchange_rate, 0) - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11") + exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling") self.assertEqual(exchange_rate, 0) def test_exchange_rate_strict_switched(self): # Start with allow_stale is True - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15") + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") self.assertEqual(exchange_rate, 65.1) frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) @@ -100,5 +108,5 @@ class TestCurrencyExchange(unittest.TestCase): # Will fetch from fixer.io self.clear_cache() - exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15") - self.assertEqual(flt(exchange_rate, 3), 67.79) \ No newline at end of file + exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") + self.assertEqual(flt(exchange_rate, 3), 67.79) diff --git a/erpnext/setup/doctype/currency_exchange/test_records.json b/erpnext/setup/doctype/currency_exchange/test_records.json index 0c9cfbb67c..152060edfc 100644 --- a/erpnext/setup/doctype/currency_exchange/test_records.json +++ b/erpnext/setup/doctype/currency_exchange/test_records.json @@ -1,44 +1,56 @@ [ - { - "doctype": "Currency Exchange", - "date": "2016-01-01", - "exchange_rate": 60.0, - "from_currency": "USD", - "to_currency": "INR" - }, - { - "doctype": "Currency Exchange", - "date": "2016-01-01", - "exchange_rate": 0.773, - "from_currency": "USD", - "to_currency": "EUR" - }, - { - "doctype": "Currency Exchange", - "date": "2016-01-01", - "exchange_rate": 0.0167, - "from_currency": "INR", - "to_currency": "USD" - }, - { - "doctype": "Currency Exchange", - "date": "2016-01-10", - "exchange_rate": 65.1, - "from_currency": "USD", - "to_currency": "INR" - }, + { + "doctype": "Currency Exchange", + "date": "2016-01-01", + "exchange_rate": 60.0, + "from_currency": "USD", + "to_currency": "INR", + "for_buying": 1, + "for_selling": 0 + }, { - "doctype": "Currency Exchange", - "date": "2016-01-30", - "exchange_rate": 62.9, - "from_currency": "USD", - "to_currency": "INR" - }, - { - "doctype": "Currency Exchange", - "date": "2016-01-10", - "exchange_rate": 65.1, - "from_currency": "INR", - "to_currency": "NGN" - } -] \ No newline at end of file + "doctype": "Currency Exchange", + "date": "2016-01-01", + "exchange_rate": 0.773, + "from_currency": "USD", + "to_currency": "EUR", + "for_buying": 0, + "for_selling": 1 + }, + { + "doctype": "Currency Exchange", + "date": "2016-01-01", + "exchange_rate": 0.0167, + "from_currency": "INR", + "to_currency": "USD", + "for_buying": 1, + "for_selling": 0 + }, + { + "doctype": "Currency Exchange", + "date": "2016-01-10", + "exchange_rate": 65.1, + "from_currency": "USD", + "to_currency": "INR", + "for_buying": 1, + "for_selling": 0 + }, + { + "doctype": "Currency Exchange", + "date": "2016-01-30", + "exchange_rate": 62.9, + "from_currency": "USD", + "to_currency": "INR", + "for_buying": 1, + "for_selling": 1 + }, + { + "doctype": "Currency Exchange", + "date": "2016-01-10", + "exchange_rate": 65.1, + "from_currency": "INR", + "to_currency": "NGN", + "for_buying": 1, + "for_selling": 1 + } +] From 88de00fb9496e8f67f2970bc35104bbb5e952584 Mon Sep 17 00:00:00 2001 From: "FinByz Tech Pvt. Ltd" Date: Thu, 17 Oct 2019 17:12:23 +0530 Subject: [PATCH 083/131] Rendered Email template in email Campaign --- erpnext/crm/doctype/email_campaign/email_campaign.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 98e4927beb..6ea24a5f20 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -73,13 +73,13 @@ def send_mail(entry, email_campaign): email_template = frappe.get_doc("Email Template", entry.get("email_template")) sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email') - + context = { "doc":frappe.get_doc(email_campaign.email_campaign_for,email_campaign.recipient) } # send mail and link communication to document comm = make( doctype = "Email Campaign", name = email_campaign.name, subject = email_template.get("subject"), - content = email_template.get("response"), + content = frappe.render_template(email_template.get("response"),context), sender = sender, recipients = recipient, communication_medium = "Email", From 4c7ac65db1174d6659547741631f141cec905931 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 11 Nov 2019 17:19:52 +0530 Subject: [PATCH 084/131] fix: Made Campaign for field mandatory --- erpnext/crm/doctype/email_campaign/email_campaign.json | 5 +++-- erpnext/crm/doctype/email_campaign/email_campaign.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json index 3259136275..736a9d6173 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.json +++ b/erpnext/crm/doctype/email_campaign/email_campaign.json @@ -52,7 +52,8 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Email Campaign For ", - "options": "\nLead\nContact" + "options": "\nLead\nContact", + "reqd": 1 }, { "fieldname": "recipient", @@ -69,7 +70,7 @@ "options": "User" } ], - "modified": "2019-07-12 13:47:37.261213", + "modified": "2019-11-11 17:18:47.342839", "modified_by": "Administrator", "module": "CRM", "name": "Email Campaign", diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 6ea24a5f20..3050d05a7c 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -73,13 +73,13 @@ def send_mail(entry, email_campaign): email_template = frappe.get_doc("Email Template", entry.get("email_template")) sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email') - context = { "doc":frappe.get_doc(email_campaign.email_campaign_for,email_campaign.recipient) } + context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)} # send mail and link communication to document comm = make( doctype = "Email Campaign", name = email_campaign.name, subject = email_template.get("subject"), - content = frappe.render_template(email_template.get("response"),context), + content = frappe.render_template(email_template.get("response"), context), sender = sender, recipients = recipient, communication_medium = "Email", From 8b2223ae5f163e4adec42429efc209717968a15c Mon Sep 17 00:00:00 2001 From: ci2014 Date: Mon, 11 Nov 2019 12:57:48 +0100 Subject: [PATCH 085/131] Move add_custom_button for Gantt and Kanban Board (#19193) Move add_custom_button for Gantt and Kanban Board to set_buttons, because in onload it is not working. --- erpnext/projects/doctype/project/project.js | 34 ++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 4a03a58f80..25c97d1fb8 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -53,23 +53,6 @@ frappe.ui.form.on("Project", { filters: filters }; }); - - if (frappe.model.can_read("Task")) { - frm.add_custom_button(__("Gantt Chart"), function () { - frappe.route_options = { - "project": frm.doc.name - }; - frappe.set_route("List", "Task", "Gantt"); - }); - - frm.add_custom_button(__("Kanban Board"), () => { - frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { - project: frm.doc.project_name - }).then(() => { - frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); - }); - }); - } }, refresh: function (frm) { @@ -97,6 +80,23 @@ frappe.ui.form.on("Project", { frm.events.set_status(frm, 'Cancelled'); }, __('Set Status')); } + + if (frappe.model.can_read("Task")) { + frm.add_custom_button(__("Gantt Chart"), function () { + frappe.route_options = { + "project": frm.doc.name + }; + frappe.set_route("List", "Task", "Gantt"); + }); + + frm.add_custom_button(__("Kanban Board"), () => { + frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { + project: frm.doc.project_name + }).then(() => { + frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); + }); + }); + } }, create_duplicate: function(frm) { From a227b9a9a657ea3db7d3851f91e37dd74827a706 Mon Sep 17 00:00:00 2001 From: Mitchy25 <42224026+Mitchy25@users.noreply.github.com> Date: Tue, 12 Nov 2019 00:59:53 +1300 Subject: [PATCH 086/131] Fix Bank Reconcilaition for Payment Entries (#19190) * Fix Bank Reconciliation with Payment Entries * Update bank_reconciliation.js --- .../page/bank_reconciliation/bank_reconciliation.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 854b973bea..efc76f9158 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -529,9 +529,16 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { frappe.db.get_doc(dt, event.value) .then(doc => { let displayed_docs = [] + let payment = [] if (dt === "Payment Entry") { payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency; payment.doctype = dt + payment.posting_date = doc.posting_date; + payment.party = doc.party; + payment.reference_no = doc.reference_no; + payment.reference_date = doc.reference_date; + payment.paid_amount = doc.paid_amount; + payment.name = doc.name; displayed_docs.push(payment); } else if (dt === "Journal Entry") { doc.accounts.forEach(payment => { @@ -564,8 +571,8 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; details_wrapper.append(frappe.render_template("linked_payment_header")); - displayed_docs.forEach(values => { - details_wrapper.append(frappe.render_template("linked_payment_row", values)); + displayed_docs.forEach(payment => { + details_wrapper.append(frappe.render_template("linked_payment_row", payment)); }) }) } From 001ee5ee1b2608aa4d2064bdb234399d01dbe79c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 11 Nov 2019 17:43:48 +0530 Subject: [PATCH 087/131] fix: dictionary changed size during iteration (#19546) --- erpnext/stock/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 2ac0bae6da..d7629176a5 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -271,6 +271,7 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto 'fieldtype': 'Currency' if d.get("convertible") == 'rate' else 'Float' }) + update_dict_values = [] for row_idx, row in enumerate(result): data = row.items() if is_dict_obj else enumerate(row) for key, value in data: @@ -286,7 +287,11 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto row.insert(key+1, new_value) else: new_key = "{0}_{1}".format(key, frappe.scrub(include_uom)) - row[new_key] = new_value + update_dict_values.append([row, new_key, new_value]) + + for data in update_dict_values: + row, key, value = data + row[key] = value def get_available_serial_nos(item_code, warehouse): return frappe.get_all("Serial No", filters = {'item_code': item_code, From 39152f935c901385267ce033bd002cadf2f472db Mon Sep 17 00:00:00 2001 From: Diksha Date: Mon, 11 Nov 2019 19:22:51 +0530 Subject: [PATCH 088/131] fix(employee): show only active employees in the error display while marking a reporting to employee as left --- erpnext/hr/doctype/employee/employee.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 3fc330e2d2..703ec06f83 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -167,10 +167,11 @@ class Employee(NestedSet): def validate_status(self): if self.status == 'Left': reports_to = frappe.db.get_all('Employee', - filters={'reports_to': self.name} + filters={'reports_to': self.name, 'status': "Active"}, + fields=['name','employee_name'] ) if reports_to: - link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name) for employee in reports_to] + link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to] throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ") + ', '.join(link_to_employees), EmployeeLeftValidationError) if not self.relieving_date: From 3cc3b57926a7062a9ae1c3bd3a28041542ee0140 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 12 Nov 2019 14:32:50 +0530 Subject: [PATCH 089/131] fix: Expense claim paid through employee advance getting fetched as outstanding in Payment Entry (#19427) * fix: Expense claim paid through employee advance getting fetched as outstanding in Payment Entry * fix: Codacy * fix: Minor UX fixes * fix: Also credit payable amount in case of advance payment * fix: Against voucher in GL enrty --- .../doctype/payment_entry/payment_entry.js | 2 +- .../hr/doctype/employee/employee_dashboard.py | 2 +- .../doctype/expense_claim/expense_claim.json | 869 +++++++++--------- .../hr/doctype/expense_claim/expense_claim.py | 30 +- 4 files changed, 464 insertions(+), 439 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 1e0b1bcbf1..adf47ed276 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -554,7 +554,7 @@ frappe.ui.form.on('Payment Entry', { frappe.flags.allocate_payment_amount = true; frm.events.validate_filters_data(frm, filters); frm.events.get_outstanding_documents(frm, filters); - }, __("Filters"), __("Get Outstanding Invoices")); + }, __("Filters"), __("Get Outstanding Documents")); }, validate_filters_data: function(frm, filters) { diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py index 162b697ac8..11ad83ba37 100644 --- a/erpnext/hr/doctype/employee/employee_dashboard.py +++ b/erpnext/hr/doctype/employee/employee_dashboard.py @@ -21,7 +21,7 @@ def get_data(): }, { 'label': _('Expense'), - 'items': ['Expense Claim', 'Travel Request'] + 'items': ['Expense Claim', 'Travel Request', 'Employee Advance'] }, { 'label': _('Benefit'), diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 4e2778f48d..5c2f490171 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -1,435 +1,436 @@ { - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2013-01-10 16:34:14", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "naming_series", - "employee", - "employee_name", - "department", - "column_break_5", - "expense_approver", - "approval_status", - "is_paid", - "expense_details", - "expenses", - "sb1", - "taxes", - "transactions_section", - "total_sanctioned_amount", - "total_taxes_and_charges", - "total_advance_amount", - "column_break_17", - "grand_total", - "total_claimed_amount", - "total_amount_reimbursed", - "section_break_16", - "posting_date", - "vehicle_log", - "task", - "cb1", - "remark", - "title", - "email_id", - "accounting_details", - "company", - "mode_of_payment", - "clearance_date", - "column_break_24", - "payable_account", - "accounting_dimensions_section", - "project", - "dimension_col_break", - "cost_center", - "more_details", - "status", - "amended_from", - "advance_payments", - "advances" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "HR-EXP-.YYYY.-", - "print_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "employee", - "fieldtype": "Link", - "in_global_search": 1, - "label": "From Employee", - "oldfieldname": "employee", - "oldfieldtype": "Link", - "options": "Employee", - "reqd": 1, - "search_index": 1 - }, - { - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Employee Name", - "oldfieldname": "employee_name", - "oldfieldtype": "Data", - "read_only": 1, - "width": "150px" - }, - { - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "label": "Department", - "options": "Department", - "read_only": 1 - }, - { - "fieldname": "column_break_5", - "fieldtype": "Column Break" - }, - { - "fieldname": "expense_approver", - "fieldtype": "Link", - "label": "Expense Approver", - "options": "User" - }, - { - "default": "Draft", - "fieldname": "approval_status", - "fieldtype": "Select", - "label": "Approval Status", - "no_copy": 1, - "options": "Draft\nApproved\nRejected", - "search_index": 1 - }, - { - "fieldname": "total_claimed_amount", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Total Claimed Amount", - "no_copy": 1, - "oldfieldname": "total_claimed_amount", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "read_only": 1, - "width": "160px" - }, - { - "fieldname": "total_sanctioned_amount", - "fieldtype": "Currency", - "label": "Total Sanctioned Amount", - "no_copy": 1, - "oldfieldname": "total_sanctioned_amount", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "read_only": 1, - "width": "160px" - }, - { - "default": "0", - "depends_on": "eval:(doc.docstatus==0 || doc.is_paid)", - "fieldname": "is_paid", - "fieldtype": "Check", - "label": "Is Paid" - }, - { - "fieldname": "expense_details", - "fieldtype": "Section Break", - "oldfieldtype": "Section Break" - }, - { - "fieldname": "expenses", - "fieldtype": "Table", - "label": "Expenses", - "oldfieldname": "expense_voucher_details", - "oldfieldtype": "Table", - "options": "Expense Claim Detail", - "reqd": 1 - }, - { - "fieldname": "sb1", - "fieldtype": "Section Break", - "options": "Simple" - }, - { - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "label": "Posting Date", - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "reqd": 1 - }, - { - "fieldname": "vehicle_log", - "fieldtype": "Link", - "label": "Vehicle Log", - "options": "Vehicle Log", - "read_only": 1 - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" - }, - { - "fieldname": "task", - "fieldtype": "Link", - "label": "Task", - "options": "Task", - "remember_last_selected_value": 1 - }, - { - "fieldname": "cb1", - "fieldtype": "Column Break" - }, - { - "fieldname": "total_amount_reimbursed", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Total Amount Reimbursed", - "no_copy": 1, - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "remark", - "fieldtype": "Small Text", - "label": "Remark", - "no_copy": 1, - "oldfieldname": "remark", - "oldfieldtype": "Small Text" - }, - { - "allow_on_submit": 1, - "default": "{employee_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1 - }, - { - "fieldname": "email_id", - "fieldtype": "Data", - "hidden": 1, - "label": "Employees Email Id", - "oldfieldname": "email_id", - "oldfieldtype": "Data", - "print_hide": 1 - }, - { - "fieldname": "accounting_details", - "fieldtype": "Section Break", - "label": "Accounting Details" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "depends_on": "is_paid", - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "label": "Mode of Payment", - "options": "Mode of Payment" - }, - { - "fieldname": "clearance_date", - "fieldtype": "Date", - "label": "Clearance Date" - }, - { - "fieldname": "column_break_24", - "fieldtype": "Column Break" - }, - { - "fieldname": "payable_account", - "fieldtype": "Link", - "label": "Payable Account", - "options": "Account" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "collapsible": 1, - "fieldname": "more_details", - "fieldtype": "Section Break", - "label": "More Details" - }, - { - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Status", - "no_copy": 1, - "options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Expense Claim", - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "width": "160px" - }, - { - "fieldname": "advance_payments", - "fieldtype": "Section Break", - "label": "Advance Payments" - }, - { - "fieldname": "advances", - "fieldtype": "Table", - "label": "Advances", - "options": "Expense Claim Advance" - }, - { - "fieldname": "total_advance_amount", - "fieldtype": "Currency", - "label": "Total Advance Amount", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - }, - { - "fieldname": "taxes", - "fieldtype": "Table", - "label": "Expense Taxes and Charges", - "options": "Expense Taxes and Charges" - }, - { - "fieldname": "section_break_16", - "fieldtype": "Section Break" - }, - { - "fieldname": "transactions_section", - "fieldtype": "Section Break" - }, - { - "fieldname": "grand_total", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Grand Total", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, - { - "fieldname": "total_taxes_and_charges", - "fieldtype": "Currency", - "label": "Total Taxes and Charges", - "options": "Company:company:default_currency", - "read_only": 1 - } - ], - "icon": "fa fa-money", - "idx": 1, - "is_submittable": 1, - "modified": "2019-06-26 18:05:52.530462", - "modified_by": "Administrator", - "module": "HR", - "name": "Expense Claim", - "name_case": "Title Case", - "owner": "harshada@webnotestech.com", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "share": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Expense Approver", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "search_fields": "employee,employee_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "employee", - "title_field": "title" - } \ No newline at end of file + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:14", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "department", + "column_break_5", + "expense_approver", + "approval_status", + "is_paid", + "expense_details", + "expenses", + "sb1", + "taxes", + "transactions_section", + "total_sanctioned_amount", + "total_taxes_and_charges", + "total_advance_amount", + "column_break_17", + "grand_total", + "total_claimed_amount", + "total_amount_reimbursed", + "section_break_16", + "posting_date", + "vehicle_log", + "task", + "cb1", + "remark", + "title", + "email_id", + "accounting_details", + "company", + "mode_of_payment", + "clearance_date", + "column_break_24", + "payable_account", + "accounting_dimensions_section", + "project", + "dimension_col_break", + "cost_center", + "more_details", + "status", + "amended_from", + "advance_payments", + "advances" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "HR-EXP-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "label": "From Employee", + "oldfieldname": "employee", + "oldfieldtype": "Link", + "options": "Employee", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Employee Name", + "oldfieldname": "employee_name", + "oldfieldtype": "Data", + "read_only": 1, + "width": "150px" + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "expense_approver", + "fieldtype": "Link", + "label": "Expense Approver", + "options": "User" + }, + { + "default": "Draft", + "fieldname": "approval_status", + "fieldtype": "Select", + "label": "Approval Status", + "no_copy": 1, + "options": "Draft\nApproved\nRejected", + "search_index": 1 + }, + { + "fieldname": "total_claimed_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Claimed Amount", + "no_copy": 1, + "oldfieldname": "total_claimed_amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1, + "width": "160px" + }, + { + "fieldname": "total_sanctioned_amount", + "fieldtype": "Currency", + "label": "Total Sanctioned Amount", + "no_copy": 1, + "oldfieldname": "total_sanctioned_amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1, + "width": "160px" + }, + { + "default": "0", + "depends_on": "eval:(doc.docstatus==0 || doc.is_paid)", + "fieldname": "is_paid", + "fieldtype": "Check", + "label": "Is Paid" + }, + { + "fieldname": "expense_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break" + }, + { + "fieldname": "expenses", + "fieldtype": "Table", + "label": "Expenses", + "oldfieldname": "expense_voucher_details", + "oldfieldtype": "Table", + "options": "Expense Claim Detail", + "reqd": 1 + }, + { + "fieldname": "sb1", + "fieldtype": "Section Break", + "options": "Simple" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "vehicle_log", + "fieldtype": "Link", + "label": "Vehicle Log", + "options": "Vehicle Log", + "read_only": 1 + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "fieldname": "task", + "fieldtype": "Link", + "label": "Task", + "options": "Task", + "remember_last_selected_value": 1 + }, + { + "fieldname": "cb1", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_amount_reimbursed", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Amount Reimbursed", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "remark", + "fieldtype": "Small Text", + "label": "Remark", + "no_copy": 1, + "oldfieldname": "remark", + "oldfieldtype": "Small Text" + }, + { + "allow_on_submit": 1, + "default": "{employee_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1 + }, + { + "fieldname": "email_id", + "fieldtype": "Data", + "hidden": 1, + "label": "Employees Email Id", + "oldfieldname": "email_id", + "oldfieldtype": "Data", + "print_hide": 1 + }, + { + "fieldname": "accounting_details", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "depends_on": "is_paid", + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, + { + "fieldname": "clearance_date", + "fieldtype": "Date", + "label": "Clearance Date" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "payable_account", + "fieldtype": "Link", + "label": "Payable Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "collapsible": 1, + "fieldname": "more_details", + "fieldtype": "Section Break", + "label": "More Details" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "no_copy": 1, + "options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Expense Claim", + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "width": "160px" + }, + { + "fieldname": "advance_payments", + "fieldtype": "Section Break", + "label": "Advance Payments" + }, + { + "fieldname": "advances", + "fieldtype": "Table", + "label": "Advances", + "options": "Expense Claim Advance" + }, + { + "fieldname": "total_advance_amount", + "fieldtype": "Currency", + "label": "Total Advance Amount", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Expense Taxes and Charges", + "options": "Expense Taxes and Charges" + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break" + }, + { + "fieldname": "transactions_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Grand Total", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "Company:company:default_currency", + "read_only": 1 + } + ], + "icon": "fa fa-money", + "idx": 1, + "is_submittable": 1, + "modified": "2019-11-08 14:13:08.964547", + "modified_by": "Administrator", + "module": "HR", + "name": "Expense Claim", + "name_case": "Title Case", + "owner": "harshada@webnotestech.com", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Expense Approver", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "search_fields": "employee,employee_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee", + "title_field": "title" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index caeb2dd946..f0036277c8 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -144,6 +144,33 @@ class ExpenseClaim(AccountsController): "against_voucher": self.name }) ) + + gl_entry.append( + self.get_gl_dict({ + "account": data.advance_account, + "debit": data.allocated_amount, + "debit_in_account_currency": data.allocated_amount, + "against": self.payable_account, + "party_type": "Employee", + "party": self.employee, + "against_voucher_type": self.doctype, + "against_voucher": self.name + }) + ) + + gl_entry.append( + self.get_gl_dict({ + "account": self.payable_account, + "credit": data.allocated_amount, + "credit_in_account_currency": data.allocated_amount, + "against": data.advance_account, + "party_type": "Employee", + "party": self.employee, + "against_voucher_type": "Employee Advance", + "against_voucher": data.employee_advance + }) + ) + self.add_tax_gl_entries(gl_entry) if self.is_paid and self.grand_total: @@ -192,9 +219,6 @@ class ExpenseClaim(AccountsController): if not self.cost_center: frappe.throw(_("Cost center is required to book an expense claim")) - if not self.payable_account: - frappe.throw(_("Please set default payable account for the company {0}").format(getlink("Company",self.company))) - if self.is_paid: if not self.mode_of_payment: frappe.throw(_("Mode of payment is required to make a payment").format(self.employee)) From 29a2e16f62e5a3927e3231b5428b26ddf69e5a71 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 12 Nov 2019 14:43:41 +0530 Subject: [PATCH 090/131] fix: Add Serial No. button not responding (#19550) --- erpnext/public/js/utils.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index ffc5e6ad36..6f43d9ef8c 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -63,12 +63,15 @@ $.extend(erpnext, { let callback = ''; let on_close = ''; - if (grid_row.doc.serial_no) { - grid_row.doc.has_serial_no = true; - } - - me.show_serial_batch_selector(grid_row.frm, grid_row.doc, - callback, on_close, true); + frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no', + (data) => { + if(data) { + grid_row.doc.has_serial_no = data.has_serial_no; + me.show_serial_batch_selector(grid_row.frm, grid_row.doc, + callback, on_close, true); + } + } + ); }); }, }); From 4fa61940097b9bbef78ec6bb54ccf77dfd5904cc Mon Sep 17 00:00:00 2001 From: MorezMartin Date: Tue, 12 Nov 2019 13:49:01 +0100 Subject: [PATCH 091/131] feat: [production_plan -> fetching item description] Fetch item description from Material Request or Sales Order (#19541) * Change packed item * Remove description field on update_packed_items * add possibility to modify description on packed items * Fetch description from Material Request or Sales Order in production plan, add the possibility to modify the description un production plan * sync with fork * Fetch description from Material Request or Sales Order in production plan, Add the possibility to modify description in production plan * code cleaning syncing fork * code cleaning syncing fork * code cleaning syncing fork * code cleaning syncing fork * rewied and add item_details.description in case of blank field --- .../production_plan/production_plan.py | 35 ++++++++++--------- .../production_plan/test_production_plan.py | 2 -- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 0f49f73dfb..5d2696933b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -99,7 +99,7 @@ class ProductionPlan(Document): self.get_mr_items() def get_so_items(self): - so_list = [d.sales_order for d in self.get("sales_orders", []) if d.sales_order] + so_list = [d.sales_order for d in self.sales_orders if d.sales_order] if not so_list: msgprint(_("Please enter Sales Orders in the above table")) return [] @@ -109,7 +109,7 @@ class ProductionPlan(Document): item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code)) items = frappe.db.sql("""select distinct parent, item_code, warehouse, - (qty - work_order_qty) * conversion_factor as pending_qty, name + (qty - work_order_qty) * conversion_factor as pending_qty, description, name from `tabSales Order Item` so_item where parent in (%s) and docstatus = 1 and qty > work_order_qty and exists (select name from `tabBOM` bom where bom.item=so_item.item_code @@ -121,7 +121,7 @@ class ProductionPlan(Document): packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse, (((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty) - as pending_qty, pi.parent_item, so_item.name + as pending_qty, pi.parent_item, pi.description, so_item.name from `tabSales Order Item` so_item, `tabPacked Item` pi where so_item.parent = pi.parent and so_item.docstatus = 1 and pi.parent_item = so_item.item_code @@ -134,7 +134,7 @@ class ProductionPlan(Document): self.calculate_total_planned_qty() def get_mr_items(self): - mr_list = [d.material_request for d in self.get("material_requests", []) if d.material_request] + mr_list = [d.material_request for d in self.material_requests if d.material_request] if not mr_list: msgprint(_("Please enter Material Requests in the above table")) return [] @@ -143,7 +143,7 @@ class ProductionPlan(Document): if self.item_code: item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code)) - items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, + items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description, (qty - ordered_qty) as pending_qty from `tabMaterial Request Item` mr_item where parent in (%s) and docstatus = 1 and qty > ordered_qty @@ -162,7 +162,7 @@ class ProductionPlan(Document): 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, - 'description': item_details and item_details.description or '', + 'description': data.description or item_details.description, 'stock_uom': item_details and item_details.stock_uom or '', 'bom_no': item_details and item_details.bom_no or '', 'planned_qty': data.pending_qty, @@ -174,10 +174,12 @@ class ProductionPlan(Document): if self.get_items_from == "Sales Order": pi.sales_order = data.parent pi.sales_order_item = data.name + pi.description = data.description elif self.get_items_from == "Material Request": pi.material_request = data.parent pi.material_request_item = data.name + pi.description = data.description def calculate_total_planned_qty(self): self.total_planned_qty = 0 @@ -195,7 +197,6 @@ class ProductionPlan(Document): for data in self.po_items: if data.name == production_plan_item: data.produced_qty = produced_qty - data.pending_qty = data.planned_qty - data.produced_qty data.db_update() self.calculate_total_produced_qty() @@ -302,6 +303,7 @@ class ProductionPlan(Document): wo_list.extend(work_orders) frappe.flags.mute_messages = False + if wo_list: wo_list = ["""%s""" % \ (p, p) for p in wo_list] @@ -309,16 +311,15 @@ class ProductionPlan(Document): else : msgprint(_("No Work Orders created")) - def make_work_order_for_sub_assembly_items(self, item): work_orders = [] bom_data = {} - get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty")) + get_sub_assembly_items(item.get("bom_no"), bom_data) for key, data in bom_data.items(): data.update({ - 'qty': data.get("stock_qty"), + 'qty': data.get("stock_qty") * item.get("qty"), 'production_plan': self.name, 'company': self.company, 'fg_warehouse': item.get("fg_warehouse"), @@ -561,7 +562,7 @@ def get_sales_orders(self): item_filter += " and so_item.item_code = %(item)s" open_so = frappe.db.sql(""" - select distinct so.name, so.transaction_date, so.customer, so.base_grand_total as grand_total + select distinct so.name, so.transaction_date, so.customer, so.base_grand_total from `tabSales Order` so, `tabSales Order Item` so_item where so_item.parent = so.name and so.docstatus = 1 and so.status not in ("Stopped", "Closed") @@ -625,7 +626,7 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): for data in po_items: planned_qty = data.get('required_qty') or data.get('planned_qty') ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty - warehouse = warehouse or data.get("warehouse") + warehouse = data.get("warehouse") or warehouse item_details = {} if data.get("bom") or data.get("bom_no"): @@ -708,11 +709,11 @@ def get_item_data(item_code): return { "bom_no": item_details.get("bom_no"), - "stock_uom": item_details.get("stock_uom"), - "description": item_details.get("description") + "stock_uom": item_details.get("stock_uom") +# "description": item_details.get("description") } -def get_sub_assembly_items(bom_no, bom_data, qty): +def get_sub_assembly_items(bom_no, bom_data): data = get_children('BOM', parent = bom_no) for d in data: if d.expandable: @@ -729,6 +730,6 @@ def get_sub_assembly_items(bom_no, bom_data, qty): }) bom_item = bom_data.get(key) - bom_item["stock_qty"] += ((d.stock_qty * qty) / d.parent_bom_qty) + bom_item["stock_qty"] += d.stock_qty - get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"]) + get_sub_assembly_items(bom_item.get("bom_no"), bom_data) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 44796417d4..f70c9cc43f 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -11,11 +11,9 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import get_sa from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestProductionPlan(unittest.TestCase): def setUp(self): - set_perpetual_inventory(0) for item in ['Test Production Item 1', 'Subassembly Item 1', 'Raw Material Item 1', 'Raw Material Item 2']: create_item(item, valuation_rate=100) From 010714757ce367be2df33172137452588409cd18 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Nov 2019 18:20:07 +0530 Subject: [PATCH 092/131] fix: '<' not supported between instances of 'str' and 'NoneType' (#19553) --- erpnext/projects/doctype/timesheet/timesheet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index bc88250c8a..c4481c9aa0 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -188,6 +188,8 @@ class Timesheet(Document): }, as_dict=True) # check internal overlap for time_log in self.time_logs: + if not (time_log.from_time or time_log.to_time): continue + if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \ args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or (args.to_time > time_log.from_time and args.to_time < time_log.to_time) or From d00c59830ef476e6ba2c5939f8aeafdc9f6805de Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 12 Nov 2019 19:17:43 +0530 Subject: [PATCH 093/131] feat: Disable CWIP Accounting checkbox added in Company and Asset Category (#19262) * feat: Disable CWIP Accounting checkbox added in Company and Asset Category Asset Settings is removed completely Disable CWIP Accounting checkbox will give priority to Asset Category * fix: Changed checkbox name to 'Enable Capital Work in Progress Accounting' - checkbox will be disabled by default - Enabling it in Company will globally enable it - When globally disabled , it's value on the asset category will be considered * chore: Added patch to set pre-existing CWIP checkbox value into new checkbox * fix(test): Asset * fix: Asset Test and Patch * fix(test): Opening Invoice Creation Tool * Update asset.py * fix: Patch and other fixes --- .../purchase_invoice/purchase_invoice.py | 29 +- erpnext/accounts/general_ledger.py | 12 +- erpnext/assets/doctype/asset/asset.js | 17 +- erpnext/assets/doctype/asset/asset.json | 994 +++++++++--------- erpnext/assets/doctype/asset/asset.py | 21 +- erpnext/assets/doctype/asset/test_asset.py | 41 +- .../asset_category/asset_category.json | 363 ++----- .../doctype/asset_category/asset_category.py | 13 + .../assets/doctype/asset_settings/__init__.py | 0 .../doctype/asset_settings/asset_settings.js | 5 - .../asset_settings/asset_settings.json | 148 --- .../doctype/asset_settings/asset_settings.py | 9 - .../asset_settings/test_asset_settings.js | 23 - .../asset_settings/test_asset_settings.py | 9 - erpnext/config/assets.py | 4 - erpnext/patches.txt | 1 + .../set_cwip_and_delete_asset_settings.py | 22 + erpnext/setup/doctype/company/company.json | 21 +- .../purchase_receipt/purchase_receipt.py | 12 +- 19 files changed, 723 insertions(+), 1021 deletions(-) delete mode 100644 erpnext/assets/doctype/asset_settings/__init__.py delete mode 100644 erpnext/assets/doctype/asset_settings/asset_settings.js delete mode 100644 erpnext/assets/doctype/asset_settings/asset_settings.json delete mode 100644 erpnext/assets/doctype/asset_settings/asset_settings.py delete mode 100644 erpnext/assets/doctype/asset_settings/test_asset_settings.js delete mode 100644 erpnext/assets/doctype/asset_settings/test_asset_settings.py create mode 100644 erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 9c1a9ece77..f1c490e2cd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -18,7 +18,7 @@ from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entri from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center -from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled +from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from frappe.model.mapper import get_mapped_doc from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ @@ -226,6 +226,8 @@ class PurchaseInvoice(BuyingController): # in case of auto inventory accounting, # expense account is always "Stock Received But Not Billed" for a stock item # except epening entry, drop-ship entry and fixed asset items + if item.item_code: + asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") if auto_accounting_for_stock and item.item_code in stock_items \ and self.is_opening == 'No' and not item.is_fixed_asset \ @@ -236,7 +238,8 @@ class PurchaseInvoice(BuyingController): item.expense_account = warehouse_account[item.warehouse]["account"] else: item.expense_account = stock_not_billed_account - elif item.is_fixed_asset and is_cwip_accounting_disabled(): + + elif item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, asset_category): if not item.asset: frappe.throw(_("Row {0}: asset is required for item {1}") .format(item.idx, item.item_code)) @@ -392,7 +395,8 @@ class PurchaseInvoice(BuyingController): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) - if not is_cwip_accounting_disabled(): + + if self.check_asset_cwip_enabled(): self.get_asset_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) @@ -405,6 +409,15 @@ class PurchaseInvoice(BuyingController): return gl_entries + def check_asset_cwip_enabled(self): + # Check if there exists any item with cwip accounting enabled in it's asset category + for item in self.get("items"): + if item.item_code and item.is_fixed_asset: + asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") + if is_cwip_accounting_enabled(self.company, asset_category): + return 1 + return 0 + def make_supplier_gl_entry(self, gl_entries): # Checked both rounding_adjustment and rounded_total # because rounded_total had value even before introcution of posting GLE based on rounded total @@ -448,6 +461,8 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) + if item.item_code: + asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items: # warehouse account @@ -490,8 +505,9 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) - elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()): + elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, + asset_category)): expense_account = (item.expense_account if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) @@ -532,7 +548,10 @@ class PurchaseInvoice(BuyingController): def get_asset_gl_entry(self, gl_entries): for item in self.get("items"): - if item.is_fixed_asset: + if item.item_code and item.is_fixed_asset : + asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") + + if item.is_fixed_asset and is_cwip_accounting_enabled(self.company, asset_category) : eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 43d9ad6435..d4dac72601 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -168,14 +168,20 @@ def validate_account_for_perpetual_inventory(gl_map): StockValueAndAccountBalanceOutOfSync) def validate_cwip_accounts(gl_map): - if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \ - and gl_map[0].voucher_type == "Journal Entry": + cwip_enabled = cint(frappe.get_cached_value("Company", + gl_map[0].company, "enable_cwip_accounting")) + + if not cwip_enabled: + cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) + + if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount where account_type = 'Capital Work in Progress' and is_group=0""")] for entry in gl_map: if entry.account in cwip_accounts: - frappe.throw(_("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) + frappe.throw( + _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) def round_off_debit_credit(gl_map): precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index c5cad73801..c7390a2ef1 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -203,7 +203,7 @@ frappe.ui.form.on('Asset', { }, opening_accumulated_depreciation: function(frm) { - erpnext.asset.set_accululated_depreciation(frm); + erpnext.asset.set_accumulated_depreciation(frm); }, make_schedules_editable: function(frm) { @@ -282,17 +282,6 @@ frappe.ui.form.on('Asset', { }, calculate_depreciation: function(frm) { - frappe.db.get_value("Asset Settings", {'name':"Asset Settings"}, 'schedule_based_on_fiscal_year', (data) => { - if (data.schedule_based_on_fiscal_year == 1) { - frm.set_df_property("depreciation_method", "options", "\nStraight Line\nManual"); - frm.toggle_reqd("available_for_use_date", true); - frm.toggle_display("frequency_of_depreciation", false); - frappe.db.get_value("Fiscal Year", {'name': frappe.sys_defaults.fiscal_year}, "year_end_date", (data) => { - frm.set_value("next_depreciation_date", data.year_end_date); - }) - } - }) - frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); }, @@ -371,12 +360,12 @@ frappe.ui.form.on('Depreciation Schedule', { }, depreciation_amount: function(frm, cdt, cdn) { - erpnext.asset.set_accululated_depreciation(frm); + erpnext.asset.set_accumulated_depreciation(frm); } }) -erpnext.asset.set_accululated_depreciation = function(frm) { +erpnext.asset.set_accumulated_depreciation = function(frm) { if(frm.doc.depreciation_method != "Manual") return; var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation); diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index c60ec5ec3f..8fda330bb7 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -1,497 +1,499 @@ { - "allow_import": 1, - "allow_rename": 1, - "autoname": "naming_series:", - "creation": "2016-03-01 17:01:27.920130", - "doctype": "DocType", - "document_type": "Document", - "field_order": [ - "naming_series", - "asset_name", - "item_code", - "item_name", - "asset_category", - "asset_owner", - "asset_owner_company", - "supplier", - "customer", - "image", - "column_break_3", - "company", - "location", - "custodian", - "department", - "purchase_date", - "disposal_date", - "journal_entry_for_scrap", - "accounting_dimensions_section", - "cost_center", - "dimension_col_break", - "section_break_5", - "gross_purchase_amount", - "available_for_use_date", - "column_break_18", - "calculate_depreciation", - "is_existing_asset", - "opening_accumulated_depreciation", - "number_of_depreciations_booked", - "section_break_23", - "finance_books", - "section_break_33", - "depreciation_method", - "value_after_depreciation", - "total_number_of_depreciations", - "column_break_24", - "frequency_of_depreciation", - "next_depreciation_date", - "section_break_14", - "schedules", - "insurance_details", - "policy_number", - "insurer", - "insured_value", - "column_break_48", - "insurance_start_date", - "insurance_end_date", - "comprehensive_insurance", - "section_break_31", - "maintenance_required", - "other_details", - "status", - "booked_fixed_asset", - "column_break_51", - "purchase_receipt", - "purchase_receipt_amount", - "purchase_invoice", - "default_finance_book", - "amended_from" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Naming Series", - "options": "ACC-ASS-.YYYY.-" - }, - { - "fieldname": "asset_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Asset Name", - "reqd": 1 - }, - { - "fieldname": "item_code", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Item Code", - "options": "Item", - "reqd": 1 - }, - { - "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Read Only", - "label": "Item Name" - }, - { - "fetch_from": "item_code.asset_category", - "fieldname": "asset_category", - "fieldtype": "Link", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Asset Category", - "options": "Asset Category", - "read_only": 1 - }, - { - "fieldname": "asset_owner", - "fieldtype": "Select", - "label": "Asset Owner", - "options": "\nCompany\nSupplier\nCustomer" - }, - { - "depends_on": "eval:doc.asset_owner == \"Company\"", - "fieldname": "asset_owner_company", - "fieldtype": "Link", - "label": "Asset Owner Company", - "options": "Company" - }, - { - "depends_on": "eval:doc.asset_owner == \"Supplier\"", - "fieldname": "supplier", - "fieldtype": "Link", - "label": "Supplier", - "options": "Supplier" - }, - { - "depends_on": "eval:doc.asset_owner == \"Customer\"", - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "options": "Customer" - }, - { - "allow_on_submit": 1, - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Image", - "no_copy": 1, - "print_hide": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "fieldname": "location", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Location", - "options": "Location", - "reqd": 1 - }, - { - "fieldname": "custodian", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Custodian", - "options": "Employee" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "department", - "fieldtype": "Link", - "label": "Department", - "options": "Department" - }, - { - "fieldname": "purchase_date", - "fieldtype": "Date", - "label": "Purchase Date", - "reqd": 1 - }, - { - "fieldname": "disposal_date", - "fieldtype": "Date", - "label": "Disposal Date", - "read_only": 1 - }, - { - "fieldname": "journal_entry_for_scrap", - "fieldtype": "Link", - "label": "Journal Entry for Scrap", - "no_copy": 1, - "options": "Journal Entry", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break" - }, - { - "fieldname": "gross_purchase_amount", - "fieldtype": "Currency", - "label": "Gross Purchase Amount", - "options": "Company:company:default_currency", - "reqd": 1 - }, - { - "fieldname": "available_for_use_date", - "fieldtype": "Date", - "label": "Available-for-use Date" - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "calculate_depreciation", - "fieldtype": "Check", - "label": "Calculate Depreciation" - }, - { - "default": "0", - "fieldname": "is_existing_asset", - "fieldtype": "Check", - "label": "Is Existing Asset" - }, - { - "depends_on": "is_existing_asset", - "fieldname": "opening_accumulated_depreciation", - "fieldtype": "Currency", - "label": "Opening Accumulated Depreciation", - "no_copy": 1, - "options": "Company:company:default_currency" - }, - { - "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)", - "fieldname": "number_of_depreciations_booked", - "fieldtype": "Int", - "label": "Number of Depreciations Booked", - "no_copy": 1 - }, - { - "depends_on": "calculate_depreciation", - "fieldname": "section_break_23", - "fieldtype": "Section Break", - "label": "Depreciation" - }, - { - "fieldname": "finance_books", - "fieldtype": "Table", - "label": "Finance Books", - "options": "Asset Finance Book" - }, - { - "fieldname": "section_break_33", - "fieldtype": "Section Break", - "hidden": 1 - }, - { - "fieldname": "depreciation_method", - "fieldtype": "Select", - "label": "Depreciation Method", - "options": "\nStraight Line\nDouble Declining Balance\nManual" - }, - { - "fieldname": "value_after_depreciation", - "fieldtype": "Currency", - "hidden": 1, - "label": "Value After Depreciation", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "total_number_of_depreciations", - "fieldtype": "Int", - "label": "Total Number of Depreciations" - }, - { - "fieldname": "column_break_24", - "fieldtype": "Column Break" - }, - { - "fieldname": "frequency_of_depreciation", - "fieldtype": "Int", - "label": "Frequency of Depreciation (Months)" - }, - { - "fieldname": "next_depreciation_date", - "fieldtype": "Date", - "label": "Next Depreciation Date", - "no_copy": 1 - }, - { - "depends_on": "calculate_depreciation", - "fieldname": "section_break_14", - "fieldtype": "Section Break", - "label": "Depreciation Schedule" - }, - { - "fieldname": "schedules", - "fieldtype": "Table", - "label": "Depreciation Schedules", - "no_copy": 1, - "options": "Depreciation Schedule" - }, - { - "collapsible": 1, - "fieldname": "insurance_details", - "fieldtype": "Section Break", - "label": "Insurance details" - }, - { - "fieldname": "policy_number", - "fieldtype": "Data", - "label": "Policy number" - }, - { - "fieldname": "insurer", - "fieldtype": "Data", - "label": "Insurer" - }, - { - "fieldname": "insured_value", - "fieldtype": "Data", - "label": "Insured value" - }, - { - "fieldname": "column_break_48", - "fieldtype": "Column Break" - }, - { - "fieldname": "insurance_start_date", - "fieldtype": "Date", - "label": "Insurance Start Date" - }, - { - "fieldname": "insurance_end_date", - "fieldtype": "Date", - "label": "Insurance End Date" - }, - { - "fieldname": "comprehensive_insurance", - "fieldtype": "Data", - "label": "Comprehensive Insurance" - }, - { - "fieldname": "section_break_31", - "fieldtype": "Section Break", - "label": "Maintenance" - }, - { - "allow_on_submit": 1, - "default": "0", - "description": "Check if Asset requires Preventive Maintenance or Calibration", - "fieldname": "maintenance_required", - "fieldtype": "Check", - "label": "Maintenance Required" - }, - { - "collapsible": 1, - "fieldname": "other_details", - "fieldtype": "Section Break", - "label": "Other Details" - }, - { - "allow_on_submit": 1, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "no_copy": 1, - "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "booked_fixed_asset", - "fieldtype": "Check", - "label": "Booked Fixed Asset", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_51", - "fieldtype": "Column Break" - }, - { - "fieldname": "purchase_receipt", - "fieldtype": "Link", - "label": "Purchase Receipt", - "no_copy": 1, - "options": "Purchase Receipt", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "purchase_receipt_amount", - "fieldtype": "Currency", - "hidden": 1, - "label": "Purchase Receipt Amount", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "purchase_invoice", - "fieldtype": "Link", - "label": "Purchase Invoice", - "no_copy": 1, - "options": "Purchase Invoice", - "read_only": 1 - }, - { - "fetch_from": "company.default_finance_book", - "fieldname": "default_finance_book", - "fieldtype": "Link", - "hidden": 1, - "label": "Default Finance Book", - "options": "Finance Book", - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Asset", - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - } - ], - "idx": 72, - "image_field": "image", - "is_submittable": 1, - "modified": "2019-05-25 22:26:19.786201", - "modified_by": "Administrator", - "module": "Assets", - "name": "Asset", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Quality Manager", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "asset_name" - } \ No newline at end of file + "allow_import": 1, + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2016-03-01 17:01:27.920130", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "asset_name", + "item_code", + "item_name", + "asset_category", + "asset_owner", + "asset_owner_company", + "supplier", + "customer", + "image", + "column_break_3", + "company", + "location", + "custodian", + "department", + "purchase_date", + "disposal_date", + "journal_entry_for_scrap", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "section_break_5", + "gross_purchase_amount", + "available_for_use_date", + "column_break_18", + "calculate_depreciation", + "is_existing_asset", + "opening_accumulated_depreciation", + "number_of_depreciations_booked", + "section_break_23", + "finance_books", + "section_break_33", + "depreciation_method", + "value_after_depreciation", + "total_number_of_depreciations", + "column_break_24", + "frequency_of_depreciation", + "next_depreciation_date", + "section_break_14", + "schedules", + "insurance_details", + "policy_number", + "insurer", + "insured_value", + "column_break_48", + "insurance_start_date", + "insurance_end_date", + "comprehensive_insurance", + "section_break_31", + "maintenance_required", + "other_details", + "status", + "booked_fixed_asset", + "column_break_51", + "purchase_receipt", + "purchase_receipt_amount", + "purchase_invoice", + "default_finance_book", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "ACC-ASS-.YYYY.-" + }, + { + "fieldname": "asset_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Asset Name", + "reqd": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Read Only", + "label": "Item Name" + }, + { + "fetch_from": "item_code.asset_category", + "fieldname": "asset_category", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Asset Category", + "options": "Asset Category", + "read_only": 1 + }, + { + "fieldname": "asset_owner", + "fieldtype": "Select", + "label": "Asset Owner", + "options": "\nCompany\nSupplier\nCustomer" + }, + { + "depends_on": "eval:doc.asset_owner == \"Company\"", + "fieldname": "asset_owner_company", + "fieldtype": "Link", + "label": "Asset Owner Company", + "options": "Company" + }, + { + "depends_on": "eval:doc.asset_owner == \"Supplier\"", + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, + { + "depends_on": "eval:doc.asset_owner == \"Customer\"", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "allow_on_submit": 1, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "fieldname": "location", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Location", + "options": "Location", + "reqd": 1 + }, + { + "fieldname": "custodian", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Custodian", + "options": "Employee" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department" + }, + { + "fieldname": "purchase_date", + "fieldtype": "Date", + "label": "Purchase Date", + "reqd": 1 + }, + { + "fieldname": "disposal_date", + "fieldtype": "Date", + "label": "Disposal Date", + "read_only": 1 + }, + { + "fieldname": "journal_entry_for_scrap", + "fieldtype": "Link", + "label": "Journal Entry for Scrap", + "no_copy": 1, + "options": "Journal Entry", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "gross_purchase_amount", + "fieldtype": "Currency", + "label": "Gross Purchase Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "available_for_use_date", + "fieldtype": "Date", + "label": "Available-for-use Date", + "reqd": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "calculate_depreciation", + "fieldtype": "Check", + "label": "Calculate Depreciation" + }, + { + "default": "0", + "fieldname": "is_existing_asset", + "fieldtype": "Check", + "label": "Is Existing Asset" + }, + { + "depends_on": "is_existing_asset", + "fieldname": "opening_accumulated_depreciation", + "fieldtype": "Currency", + "label": "Opening Accumulated Depreciation", + "no_copy": 1, + "options": "Company:company:default_currency" + }, + { + "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)", + "fieldname": "number_of_depreciations_booked", + "fieldtype": "Int", + "label": "Number of Depreciations Booked", + "no_copy": 1 + }, + { + "depends_on": "calculate_depreciation", + "fieldname": "section_break_23", + "fieldtype": "Section Break", + "label": "Depreciation" + }, + { + "fieldname": "finance_books", + "fieldtype": "Table", + "label": "Finance Books", + "options": "Asset Finance Book" + }, + { + "fieldname": "section_break_33", + "fieldtype": "Section Break", + "hidden": 1 + }, + { + "fieldname": "depreciation_method", + "fieldtype": "Select", + "label": "Depreciation Method", + "options": "\nStraight Line\nDouble Declining Balance\nManual" + }, + { + "fieldname": "value_after_depreciation", + "fieldtype": "Currency", + "hidden": 1, + "label": "Value After Depreciation", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "total_number_of_depreciations", + "fieldtype": "Int", + "label": "Total Number of Depreciations" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "frequency_of_depreciation", + "fieldtype": "Int", + "label": "Frequency of Depreciation (Months)" + }, + { + "fieldname": "next_depreciation_date", + "fieldtype": "Date", + "label": "Next Depreciation Date", + "no_copy": 1 + }, + { + "depends_on": "calculate_depreciation", + "fieldname": "section_break_14", + "fieldtype": "Section Break", + "label": "Depreciation Schedule" + }, + { + "fieldname": "schedules", + "fieldtype": "Table", + "label": "Depreciation Schedules", + "no_copy": 1, + "options": "Depreciation Schedule" + }, + { + "collapsible": 1, + "fieldname": "insurance_details", + "fieldtype": "Section Break", + "label": "Insurance details" + }, + { + "fieldname": "policy_number", + "fieldtype": "Data", + "label": "Policy number" + }, + { + "fieldname": "insurer", + "fieldtype": "Data", + "label": "Insurer" + }, + { + "fieldname": "insured_value", + "fieldtype": "Data", + "label": "Insured value" + }, + { + "fieldname": "column_break_48", + "fieldtype": "Column Break" + }, + { + "fieldname": "insurance_start_date", + "fieldtype": "Date", + "label": "Insurance Start Date" + }, + { + "fieldname": "insurance_end_date", + "fieldtype": "Date", + "label": "Insurance End Date" + }, + { + "fieldname": "comprehensive_insurance", + "fieldtype": "Data", + "label": "Comprehensive Insurance" + }, + { + "fieldname": "section_break_31", + "fieldtype": "Section Break", + "label": "Maintenance" + }, + { + "allow_on_submit": 1, + "default": "0", + "description": "Check if Asset requires Preventive Maintenance or Calibration", + "fieldname": "maintenance_required", + "fieldtype": "Check", + "label": "Maintenance Required" + }, + { + "collapsible": 1, + "fieldname": "other_details", + "fieldtype": "Section Break", + "label": "Other Details" + }, + { + "allow_on_submit": 1, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "booked_fixed_asset", + "fieldtype": "Check", + "label": "Booked Fixed Asset", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_51", + "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_receipt", + "fieldtype": "Link", + "label": "Purchase Receipt", + "no_copy": 1, + "options": "Purchase Receipt", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "purchase_receipt_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Purchase Receipt Amount", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "no_copy": 1, + "options": "Purchase Invoice", + "read_only": 1 + }, + { + "fetch_from": "company.default_finance_book", + "fieldname": "default_finance_book", + "fieldtype": "Link", + "hidden": 1, + "label": "Default Finance Book", + "options": "Finance Book", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Asset", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + } + ], + "idx": 72, + "image_field": "image", + "is_submittable": 1, + "modified": "2019-10-07 15:34:30.976208", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "asset_name" +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 6e2bbc1626..94e6f6168e 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -30,7 +30,8 @@ class Asset(AccountsController): self.validate_in_use_date() self.set_status() self.update_stock_movement() - if not self.booked_fixed_asset and not is_cwip_accounting_disabled(): + if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.company, + self.asset_category): self.make_gl_entries() def on_cancel(self): @@ -76,10 +77,13 @@ class Asset(AccountsController): self.set('finance_books', finance_books) def validate_asset_values(self): + if not self.asset_category: + self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") + if not flt(self.gross_purchase_amount): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) - if not is_cwip_accounting_disabled(): + if is_cwip_accounting_enabled(self.company, self.asset_category): if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}"). format(self.item_code)) @@ -424,7 +428,7 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): - if is_cwip_accounting_disabled(): + if not is_cwip_accounting_enabled(self.company, self.asset_category): return assets = frappe.db.sql_list(""" select name from `tabAsset` @@ -574,8 +578,13 @@ def make_journal_entry(asset_name): return je -def is_cwip_accounting_disabled(): - return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")) +def is_cwip_accounting_enabled(company, asset_category=None): + enable_cwip_in_company = cint(frappe.db.get_value("Company", company, "enable_cwip_accounting")) + + if enable_cwip_in_company or not asset_category: + return enable_cwip_in_company + + return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): days = date_diff(to_date, from_date) @@ -587,4 +596,4 @@ def get_total_days(date, frequency): period_start_date = add_months(date, cint(frequency) * -1) - return date_diff(date, period_start_date) \ No newline at end of file + return date_diff(date, period_start_date) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c09b94fa8e..7085b31e05 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -14,7 +14,6 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas class TestAsset(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() - remove_prorated_depreciation_schedule() create_asset_data() frappe.db.sql("delete from `tabTax Rule`") @@ -70,11 +69,13 @@ class TestAsset(unittest.TestCase): {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) def test_is_fixed_asset_set(self): + asset = create_asset(is_existing_asset = 1) doc = frappe.new_doc('Purchase Invoice') doc.supplier = '_Test Supplier' doc.append('items', { 'item_code': 'Macbook Pro', - 'qty': 1 + 'qty': 1, + 'asset': asset.name }) doc.set_missing_values() @@ -200,7 +201,6 @@ class TestAsset(unittest.TestCase): self.assertEqual(schedules, expected_schedules) def test_schedule_for_prorated_straight_line_method(self): - set_prorated_depreciation_schedule() pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -233,8 +233,6 @@ class TestAsset(unittest.TestCase): self.assertEqual(schedules, expected_schedules) - remove_prorated_depreciation_schedule() - def test_depreciation(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -487,6 +485,8 @@ class TestAsset(unittest.TestCase): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_purchase_invoice_from_pr) + #frappe.db.set_value("Asset Category","Computers","enable_cwip_accounting", 1) + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location") @@ -565,6 +565,7 @@ class TestAsset(unittest.TestCase): where voucher_type='Asset' and voucher_no = %s order by account""", asset_doc.name) + self.assertEqual(gle, expected_gle) def test_expense_head(self): @@ -575,7 +576,6 @@ class TestAsset(unittest.TestCase): self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) - def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -596,15 +596,15 @@ def create_asset(**args): asset = frappe.get_doc({ "doctype": "Asset", - "asset_name": "Macbook Pro 1", + "asset_name": args.asset_name or "Macbook Pro 1", "asset_category": "Computers", - "item_code": "Macbook Pro", - "company": "_Test Company", + "item_code": args.item_code or "Macbook Pro", + "company": args.company or"_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": 0, "gross_purchase_amount": 100000, "expected_value_after_useful_life": 10000, - "warehouse": "_Test Warehouse - _TC", + "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": "2020-06-06", "location": "Test Location", "asset_owner": "Company", @@ -616,6 +616,9 @@ def create_asset(**args): except frappe.DuplicateEntryError: pass + if args.submit: + asset.submit() + return asset def create_asset_category(): @@ -623,6 +626,7 @@ def create_asset_category(): asset_category.asset_category_name = "Computers" asset_category.total_number_of_depreciations = 3 asset_category.frequency_of_depreciation = 3 + asset_category.enable_cwip_accounting = 1 asset_category.append("accounts", { "company_name": "_Test Company", "fixed_asset_account": "_Test Fixed Asset - _TC", @@ -656,19 +660,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) - -def remove_prorated_depreciation_schedule(): - asset_settings = frappe.get_doc("Asset Settings", "Asset Settings") - asset_settings.schedule_based_on_fiscal_year = 0 - asset_settings.save() - - frappe.db.commit() - -def set_prorated_depreciation_schedule(): - asset_settings = frappe.get_doc("Asset Settings", "Asset Settings") - asset_settings.schedule_based_on_fiscal_year = 1 - asset_settings.number_of_days_in_fiscal_year = 360 - asset_settings.save() - - frappe.db.commit() + 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/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json index 882cbe2eaa..7483b41d4d 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.json +++ b/erpnext/assets/doctype/asset_category/asset_category.json @@ -1,284 +1,115 @@ { - "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", + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:asset_category_name", + "creation": "2016-03-01 17:41:39.778765", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "asset_category_name", + "column_break_3", + "depreciation_options", + "enable_cwip_accounting", + "finance_book_detail", + "finance_books", + "section_break_2", + "accounts" + ], "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 - }, + "fieldname": "asset_category_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Asset Category Name", + "reqd": 1, + "unique": 1 + }, { - "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 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "finance_book_detail", - "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": "Finance Book Detail", - "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 - }, + "fieldname": "finance_book_detail", + "fieldtype": "Section Break", + "label": "Finance Book Detail" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "finance_books", - "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": "Finance Books", - "length": 0, - "no_copy": 0, - "options": "Asset Finance Book", - "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 - }, + "fieldname": "finance_books", + "fieldtype": "Table", + "label": "Finance Books", + "options": "Asset Finance Book" + }, { - "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 - }, + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Accounts" + }, { - "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 + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Asset Category Account", + "reqd": 1 + }, + { + "fieldname": "depreciation_options", + "fieldtype": "Section Break", + "label": "Depreciation Options" + }, + { + "default": "0", + "fieldname": "enable_cwip_accounting", + "fieldtype": "Check", + "label": "Enable Capital Work in Progress Accounting" } - ], - "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": "2018-05-12 14:56:04.116425", - "modified_by": "Administrator", - "module": "Assets", - "name": "Asset Category", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-10-11 12:19:59.759136", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Category", + "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "share": 1, "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 + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ 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 index bbdc6ec2cf..5cb634abcd 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -10,11 +10,24 @@ from frappe.model.document import Document class AssetCategory(Document): def validate(self): + self.validate_finance_books() + self.validate_enable_cwip_accounting() + + def validate_finance_books(self): for d in self.finance_books: for field in ("Total Number of Depreciations", "Frequency of Depreciation"): if cint(d.get(frappe.scrub(field)))<1: frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) + def validate_enable_cwip_accounting(self): + if self.enable_cwip_accounting : + for d in self.accounts: + cwip = frappe.db.get_value("Company",d.company_name,"enable_cwip_accounting") + if cwip: + frappe.throw(_ + ("CWIP is enabled globally in Company {1}. To enable it in Asset Category, first disable it in {1} ").format( + frappe.bold(d.idx), frappe.bold(d.company_name))) + @frappe.whitelist() def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None): if not asset_category and company: diff --git a/erpnext/assets/doctype/asset_settings/__init__.py b/erpnext/assets/doctype/asset_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.js b/erpnext/assets/doctype/asset_settings/asset_settings.js deleted file mode 100644 index 3b421486c3..0000000000 --- a/erpnext/assets/doctype/asset_settings/asset_settings.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Asset Settings', { -}); diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json deleted file mode 100644 index edc5ce169c..0000000000 --- a/erpnext/assets/doctype/asset_settings/asset_settings.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-01-03 10:30:32.983381", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "depreciation_options", - "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 Options", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "disable_cwip_accounting", - "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": "Disable CWIP Accounting", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-05-26 18:31:19.930563", - "modified_by": "Administrator", - "module": "Assets", - "name": "Asset Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Accounts 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, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.py b/erpnext/assets/doctype/asset_settings/asset_settings.py deleted file mode 100644 index e303ebd23f..0000000000 --- a/erpnext/assets/doctype/asset_settings/asset_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -from frappe.model.document import Document - -class AssetSettings(Document): - pass diff --git a/erpnext/assets/doctype/asset_settings/test_asset_settings.js b/erpnext/assets/doctype/asset_settings/test_asset_settings.js deleted file mode 100644 index eac2c928f3..0000000000 --- a/erpnext/assets/doctype/asset_settings/test_asset_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Settings - () => frappe.tests.make('Asset Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_settings/test_asset_settings.py b/erpnext/assets/doctype/asset_settings/test_asset_settings.py deleted file mode 100644 index 75f146a27e..0000000000 --- a/erpnext/assets/doctype/asset_settings/test_asset_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -class TestAssetSettings(unittest.TestCase): - pass diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py index 3c9452f5a4..4cf7cf0806 100644 --- a/erpnext/config/assets.py +++ b/erpnext/config/assets.py @@ -21,10 +21,6 @@ def get_data(): "name": "Asset Category", "onboard": 1, }, - { - "type": "doctype", - "name": "Asset Settings", - }, { "type": "doctype", "name": "Asset Movement", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index aeb12f51b5..0155b27820 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -640,6 +640,7 @@ erpnext.patches.v12_0.create_default_energy_point_rules erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type +erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py new file mode 100644 index 0000000000..3d07fe57a5 --- /dev/null +++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py @@ -0,0 +1,22 @@ +from __future__ import unicode_literals +import frappe +from frappe.utils import cint + + +def execute(): + '''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field + in Company, delete Asset Settings ''' + + if frappe.db.exists("DocType","Asset Settings"): + frappe.reload_doctype("Company") + cwip_value = frappe.db.sql(""" SELECT value FROM `tabSingles` WHERE doctype='Asset Settings' + and field='disable_cwip_accounting' """, as_dict=1) + + companies = [x['name'] for x in frappe.get_all("Company", "name")] + for company in companies: + enable_cwip_accounting = cint(not cint(cwip_value[0]['value'])) + frappe.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting) + + frappe.db.sql( + """ DELETE FROM `tabSingles` where doctype = 'Asset Settings' """) + frappe.delete_doc_if_exists("DocType","Asset Settings") \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index bc3418997d..2d181b53ca 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -72,6 +72,7 @@ "stock_received_but_not_billed", "expenses_included_in_valuation", "fixed_asset_depreciation_settings", + "enable_cwip_accounting", "accumulated_depreciation_account", "depreciation_expense_account", "series_for_depreciation_entry", @@ -720,12 +721,18 @@ "fieldtype": "Link", "label": "Default Buying Terms", "options": "Terms and Conditions" + }, + { + "default": "0", + "fieldname": "enable_cwip_accounting", + "fieldtype": "Check", + "label": "Enable Capital Work in Progress Accounting" } ], "icon": "fa fa-building", "idx": 1, "image_field": "company_logo", - "modified": "2019-07-04 22:20:45.104307", + "modified": "2019-10-09 14:42:04.440974", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -767,6 +774,18 @@ { "read": 1, "role": "Projects User" + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 } ], "show_name_in_global_search": 1, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 3362d4b0f5..1bfdca50ea 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -14,7 +14,7 @@ from erpnext.accounts.utils import get_account_currency from frappe.desk.notifications import clear_doctype_notifications from frappe.model.mapper import get_mapped_doc from erpnext.buying.utils import check_on_hold_or_closed_status -from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled +from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from six import iteritems @@ -338,12 +338,13 @@ class PurchaseReceipt(BuyingController): def get_asset_gl_entry(self, gl_entries, expenses_included_in_valuation=None): arbnb_account, cwip_account = None, None - cwip_disabled = is_cwip_accounting_disabled() - if not expenses_included_in_valuation: expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") for d in self.get("items"): + asset_category = frappe.get_cached_value("Item", d.item_code, "asset_category") + cwip_enabled = is_cwip_accounting_enabled(self.company, asset_category) + if d.is_fixed_asset and not (arbnb_account and cwip_account): arbnb_account = self.get_company_default("asset_received_but_not_billed") @@ -351,8 +352,7 @@ class PurchaseReceipt(BuyingController): cwip_account = get_asset_account("capital_work_in_progress_account", d.asset, company = self.company) - if d.is_fixed_asset and not cwip_disabled: - + if d.is_fixed_asset and cwip_enabled: asset_amount = flt(d.net_amount) + flt(d.item_tax_amount/self.conversion_rate) base_asset_amount = flt(d.base_net_amount + d.item_tax_amount) @@ -381,7 +381,7 @@ class PurchaseReceipt(BuyingController): if d.is_fixed_asset and flt(d.landed_cost_voucher_amount): asset_account = (get_asset_category_account(d.asset, 'fixed_asset_account', - company = self.company) if cwip_disabled else cwip_account) + company = self.company) if not cwip_enabled else cwip_account) gl_entries.append(self.get_gl_dict({ "account": expenses_included_in_valuation, From 06c812957415e34b52d0aee657b465da107a2320 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 13 Nov 2019 10:51:43 +0530 Subject: [PATCH 094/131] fix(batch): fetch company on splitting the batch (#19558) --- erpnext/stock/doctype/batch/batch.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index f609a0be7d..3e890b4dd4 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -185,9 +185,17 @@ def get_batches_by_oldest(item_code, warehouse): def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None): """Split the batch into a new batch""" batch = frappe.get_doc(dict(doctype='Batch', item=item_code, batch_id=new_batch_id)).insert() + + company = frappe.db.get_value('Stock Ledger Entry', dict( + item_code=item_code, + batch_no=batch_no, + warehouse=warehouse + ), ['company']) + stock_entry = frappe.get_doc(dict( doctype='Stock Entry', purpose='Repack', + company=company, items=[ dict( item_code=item_code, From ffbfaf7099117a1e3a634b7c42d0d9dcb1dcd7df Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 10:59:23 +0530 Subject: [PATCH 095/131] fix: email digest showing incorrect upcoming events (#19552) --- .../doctype/email_digest/email_digest.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 1de5ccb142..0bcddc2151 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -4,8 +4,8 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import fmt_money, formatdate, format_time, now_datetime, \ - get_url_to_form, get_url_to_list, flt, get_link_to_report +from frappe.utils import (fmt_money, formatdate, format_time, now_datetime, + get_url_to_form, get_url_to_list, flt, get_link_to_report, add_to_date, today) from datetime import timedelta from dateutil.relativedelta import relativedelta from frappe.core.doctype.user.user import STANDARD_USERS @@ -151,8 +151,9 @@ class EmailDigest(Document): def get_calendar_events(self): """Get calendar events for given user""" from frappe.desk.doctype.event.event import get_events - events = get_events(self.future_from_date.strftime("%Y-%m-%d"), - self.future_to_date.strftime("%Y-%m-%d")) or [] + from_date, to_date = get_future_date_for_calendaer_event(self.frequency) + + events = get_events(from_date, to_date) event_count = 0 for i, e in enumerate(events): @@ -825,4 +826,14 @@ def get_count_for_period(account, fieldname, from_date, to_date): last_year_closing_count = get_count_on(account, fieldname, fy_start_date - timedelta(days=1)) count = count_on_to_date + (last_year_closing_count - count_before_from_date) - return count \ No newline at end of file + return count + +def get_future_date_for_calendaer_event(frequency): + from_date = to_date = today() + + if frequency == "Weekly": + to_date = add_to_date(from_date, weeks=1) + elif frequency == "Monthly": + to_date = add_to_date(from_date, months=1) + + return from_date, to_date \ No newline at end of file From 5ea4328359a526721206d91c150fae38329b797d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 13 Nov 2019 12:46:19 +0530 Subject: [PATCH 096/131] fix: Accumulated Values filter disappearing --- erpnext/accounts/report/balance_sheet/balance_sheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 4bc29da2c7..8c11514aa6 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Balance Sheet"] = erpnext.financial_statements; + frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements); frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", From 3a72cb46bce2168703d976a782a7bdaa9eb1d0de Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 13 Nov 2019 17:58:10 +0530 Subject: [PATCH 097/131] fix: Set due date in accounts receivable based on payment terms (#19563) --- .../report/accounts_receivable/accounts_receivable.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index bcbd427186..14906f2c2e 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -188,7 +188,11 @@ class ReceivablePayableReport(object): self.data.append(row) def set_invoice_details(self, row): - row.update(self.invoice_details.get(row.voucher_no, {})) + invoice_details = self.invoice_details.get(row.voucher_no, {}) + if row.due_date: + invoice_details.pop("due_date", None) + row.update(invoice_details) + if row.voucher_type == 'Sales Invoice': if self.filters.show_delivery_notes: self.set_delivery_notes(row) From ba8fc21594eafcd7accf60a29aca6d14b48485e8 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 18:11:58 +0530 Subject: [PATCH 098/131] fix: merge similar entries for serialized items in stock reconciliation (#19408) --- .../stock_reconciliation.py | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 98a8c59483..daf320e40a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -52,9 +52,10 @@ class StockReconciliation(StockController): def _changed(item): item_dict = get_stock_balance_for(item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no) - if (((item.qty is None or item.qty==item_dict.get("qty")) and - (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no) - or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))): + + if ((item.qty is None or item.qty==item_dict.get("qty")) and + (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and + (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")) )): return False else: # set default as current rates @@ -182,9 +183,11 @@ class StockReconciliation(StockController): from erpnext.stock.stock_ledger import get_previous_sle sl_entries = [] + has_serial_no = False for row in self.items: item = frappe.get_doc("Item", row.item_code) if item.has_serial_no or item.has_batch_no: + has_serial_no = True self.get_sle_for_serialized_items(row, sl_entries) else: previous_sle = get_previous_sle({ @@ -212,8 +215,14 @@ class StockReconciliation(StockController): sl_entries.append(self.get_sle_for_items(row)) if sl_entries: + if has_serial_no: + sl_entries = self.merge_similar_item_serial_nos(sl_entries) + self.make_sl_entries(sl_entries) + if has_serial_no and sl_entries: + self.update_valuation_rate_for_serial_no() + def get_sle_for_serialized_items(self, row, sl_entries): from erpnext.stock.stock_ledger import get_previous_sle @@ -275,8 +284,18 @@ class StockReconciliation(StockController): # update valuation rate self.update_valuation_rate_for_serial_nos(row, serial_nos) + def update_valuation_rate_for_serial_no(self): + for d in self.items: + if not d.serial_no: continue + + serial_nos = get_serial_nos(d.serial_no) + self.update_valuation_rate_for_serial_nos(d, serial_nos) + def update_valuation_rate_for_serial_nos(self, row, serial_nos): valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate + if valuation_rate is None: + return + for d in serial_nos: frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate) @@ -321,11 +340,17 @@ class StockReconciliation(StockController): where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) sl_entries = [] + + has_serial_no = False for row in self.items: if row.serial_no or row.batch_no or row.current_serial_no: + has_serial_no = True self.get_sle_for_serialized_items(row, sl_entries) if sl_entries: + if has_serial_no: + sl_entries = self.merge_similar_item_serial_nos(sl_entries) + sl_entries.reverse() allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) @@ -339,6 +364,35 @@ class StockReconciliation(StockController): "posting_time": self.posting_time }) + def merge_similar_item_serial_nos(self, sl_entries): + # If user has put the same item in multiple row with different serial no + new_sl_entries = [] + merge_similar_entries = {} + + for d in sl_entries: + if not d.serial_no or d.actual_qty < 0: + new_sl_entries.append(d) + continue + + key = (d.item_code, d.warehouse) + if key not in merge_similar_entries: + merge_similar_entries[key] = d + elif d.serial_no: + data = merge_similar_entries[key] + data.actual_qty += d.actual_qty + data.qty_after_transaction += d.qty_after_transaction + + data.valuation_rate = (data.valuation_rate + d.valuation_rate) / data.actual_qty + data.serial_no += '\n' + d.serial_no + + if data.incoming_rate: + data.incoming_rate = (data.incoming_rate + d.incoming_rate) / data.actual_qty + + for key, value in merge_similar_entries.items(): + new_sl_entries.append(value) + + return new_sl_entries + def get_gl_entries(self, warehouse_account=None): if not self.cost_center: msgprint(_("Please enter Cost Center"), raise_exception=1) From d064505ebe68b6ea6a89bfcf0bbaf1c0ec20d074 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 18:17:48 +0530 Subject: [PATCH 099/131] fix: incorrect produced qty in the production plan (#19569) --- .../doctype/production_plan/production_plan.js | 5 +++++ erpnext/manufacturing/doctype/work_order/work_order.py | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 51989378d8..3b24d0fa0f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -3,6 +3,11 @@ frappe.ui.form.on('Production Plan', { setup: function(frm) { + frm.custom_make_buttons = { + 'Work Order': 'Work Order', + 'Material Request': 'Material Request', + }; + frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) { return { filters: { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index ae4d9be282..6ea3dc83ed 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -223,7 +223,15 @@ class WorkOrder(Document): def update_production_plan_status(self): production_plan = frappe.get_doc('Production Plan', self.production_plan) - production_plan.run_method("update_produced_qty", self.produced_qty, self.production_plan_item) + produced_qty = 0 + if self.production_plan_item: + total_qty = frappe.get_all("Work Order", fields = "sum(produced_qty) as produced_qty", + filters = {'docstatus': 1, 'production_plan': self.production_plan, + 'production_plan_item': self.production_plan_item}, as_list=1) + + produced_qty = total_qty[0][0] if total_qty else 0 + + production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item) def on_submit(self): if not self.wip_warehouse: From 732d6afad55c553a7f04ef7227125ee98017887f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 13 Nov 2019 18:49:23 +0530 Subject: [PATCH 100/131] fix: Show AR summary based on outstanding (#19573) --- .../accounts_receivable_summary/accounts_receivable_summary.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index b90a7a9501..8955830e09 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.filters.report_date) or {} for party, party_dict in iteritems(self.party_total): + if party_dict.outstanding <= 0: + continue + row = frappe._dict() row.party = party From 94565d69d100cfab12bdada14013c48f63586329 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 18:58:22 +0530 Subject: [PATCH 101/131] fix: travis failing (#19568) --- erpnext/setup/doctype/currency_exchange/currency_exchange.py | 4 ++++ .../setup/doctype/currency_exchange/test_currency_exchange.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py index 60d367a4bb..6480f60f59 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py @@ -14,10 +14,14 @@ class CurrencyExchange(Document): purpose = "" if not self.date: self.date = nowdate() + + # If both selling and buying enabled + purpose = "Selling-Buying" if cint(self.for_buying)==0 and cint(self.for_selling)==1: purpose = "Selling" if cint(self.for_buying)==1 and cint(self.for_selling)==0: purpose = "Buying" + self.name = '{0}-{1}-{2}{3}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"), self.from_currency, self.to_currency, ("-" + purpose) if purpose else "") diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 857f666b2a..c5c01c5775 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -11,7 +11,9 @@ test_records = frappe.get_test_records('Currency Exchange') def save_new_records(test_records): for record in test_records: - purpose = str("") + # If both selling and buying enabled + purpose = "Selling-Buying" + if cint(record.get("for_buying"))==0 and cint(record.get("for_selling"))==1: purpose = "Selling" if cint(record.get("for_buying"))==1 and cint(record.get("for_selling"))==0: From 3e515e704ddfde9c733d96edbc8502d0f03c677c Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 13 Nov 2019 19:00:24 +0530 Subject: [PATCH 102/131] Monthly distribution of depreciation amount (#19493) * feat: allow monthly distribution of depreciation amount * chore: added comments * fix: monthly depr was not starting from use date's month --- erpnext/assets/doctype/asset/asset.json | 13 +++-- erpnext/assets/doctype/asset/asset.py | 72 +++++++++++++++++++++---- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 8fda330bb7..6882f6a992 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -33,6 +33,7 @@ "available_for_use_date", "column_break_18", "calculate_depreciation", + "allow_monthly_depreciation", "is_existing_asset", "opening_accumulated_depreciation", "number_of_depreciations_booked", @@ -216,8 +217,7 @@ { "fieldname": "available_for_use_date", "fieldtype": "Date", - "label": "Available-for-use Date", - "reqd": 1 + "label": "Available-for-use Date" }, { "fieldname": "column_break_18", @@ -450,12 +450,19 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "calculate_depreciation", + "fieldname": "allow_monthly_depreciation", + "fieldtype": "Check", + "label": "Allow Monthly Depreciation" } ], "idx": 72, "image_field": "image", "is_submittable": 1, - "modified": "2019-10-07 15:34:30.976208", + "modified": "2019-10-22 15:47:36.050828", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 94e6f6168e..d1f8c1a8d3 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext, math, json from frappe import _ from six import string_types -from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days +from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days from frappe.model.document import Document from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ @@ -149,19 +149,31 @@ class Asset(AccountsController): schedule_date = add_months(d.depreciation_start_date, n * cint(d.frequency_of_depreciation)) + # schedule date will be a year later from start date + # so monthly schedule date is calculated by removing 11 months from it + monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1) + # For first row if has_pro_rata and n==0: - depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount, + depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) + + # For first depr schedule date will be the start date + # so monthly schedule date is calculated by removing month difference between use date and start date + monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1) + # For last row elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) - depreciation_amount, days = get_pro_rata_amt(d, + depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, schedule_date, to_date) + monthly_schedule_date = add_months(schedule_date, 1) + schedule_date = add_days(schedule_date, days) + last_schedule_date = schedule_date if not depreciation_amount: continue value_after_depreciation -= flt(depreciation_amount, @@ -175,13 +187,50 @@ class Asset(AccountsController): skip_row = True if depreciation_amount > 0: - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + # With monthly depreciation, each depreciation is divided by months remaining until next date + if self.allow_monthly_depreciation: + # month range is 1 to 12 + # In pro rata case, for first and last depreciation, month range would be different + month_range = months \ + if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ + else d.frequency_of_depreciation + + for r in range(month_range): + if (has_pro_rata and n == 0): + # For first entry of monthly depr + if r == 0: + days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + per_day_amt = depreciation_amount / days + depreciation_amount_for_current_month = per_day_amt * days_until_first_depr + depreciation_amount -= depreciation_amount_for_current_month + date = monthly_schedule_date + amount = depreciation_amount_for_current_month + else: + date = add_months(monthly_schedule_date, r) + amount = depreciation_amount / (month_range - 1) + elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1: + # For last entry of monthly depr + date = last_schedule_date + amount = depreciation_amount / month_range + else: + date = add_months(monthly_schedule_date, r) + amount = depreciation_amount / month_range + + self.append("schedules", { + "schedule_date": date, + "depreciation_amount": amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + else: + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) def check_is_pro_rata(self, row): has_pro_rata = False @@ -588,9 +637,10 @@ def is_cwip_accounting_enabled(company, asset_category=None): def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): days = date_diff(to_date, from_date) + months = month_diff(to_date, from_date) total_days = get_total_days(to_date, row.frequency_of_depreciation) - return (depreciation_amount * flt(days)) / flt(total_days), days + return (depreciation_amount * flt(days)) / flt(total_days), days, months def get_total_days(date, frequency): period_start_date = add_months(date, From af7fe1937ec77028a770b490c942cbd014c36026 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 13 Nov 2019 19:00:56 +0530 Subject: [PATCH 103/131] fix: fetch leave approver defined in employee in leave application (#19559) * fix: fetch leave approver defined in employee in leave application * Update department_approver.py --- .../department_approver/department_approver.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 9f2f2013a7..7bf9905d07 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -19,14 +19,20 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approvers = [] department_details = {} department_list = [] - employee_department = filters.get("department") or frappe.get_value("Employee", filters.get("employee"), "department") + employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) + if employee.leave_approver: + approver = frappe.db.get_value("User", leave_approver, ['name', 'first_name', 'last_name']) + approvers.append(approver) + return approvers + + employee_department = filters.get("department") or employee.department if employee_department: department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True) if department_details: department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s and rgt >= %s and disabled=0 - order by lft desc""", (department_details.lft, department_details.rgt), as_list = True) + order by lft desc""", (department_details.lft, department_details.rgt), as_list=True) if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" @@ -41,4 +47,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and approver.parentfield = %s and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) - return approvers \ No newline at end of file + return approvers From 9ffa9d4a64eb60ca02da187646af34df1a723fc4 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Nov 2019 19:10:20 +0530 Subject: [PATCH 104/131] fix(patch): Enable CWIP Accounting --- .../patches/v12_0/set_cwip_and_delete_asset_settings.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py index 3d07fe57a5..5842e9edbf 100644 --- a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py +++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py @@ -9,13 +9,12 @@ def execute(): if frappe.db.exists("DocType","Asset Settings"): frappe.reload_doctype("Company") - cwip_value = frappe.db.sql(""" SELECT value FROM `tabSingles` WHERE doctype='Asset Settings' - and field='disable_cwip_accounting' """, as_dict=1) + cwip_value = frappe.db.get_single_value("Asset Settings","disable_cwip_accounting") companies = [x['name'] for x in frappe.get_all("Company", "name")] for company in companies: - enable_cwip_accounting = cint(not cint(cwip_value[0]['value'])) - frappe.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting) + enable_cwip_accounting = cint(not cint(cwip_value)) + frappe.db.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting) frappe.db.sql( """ DELETE FROM `tabSingles` where doctype = 'Asset Settings' """) From 1ad2d4a962cb0bd6b43d255b3ade228e8135d932 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 13 Nov 2019 19:21:53 +0530 Subject: [PATCH 105/131] fix: get tags for rfq (#19564) * fix: get tags for rfq * chore: remove console log --- .../request_for_quotation/request_for_quotation.js | 2 +- .../request_for_quotation/request_for_quotation.py | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 9ad06f9b7e..2f0cfa64fc 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -134,7 +134,7 @@ frappe.ui.form.on("Request for Quotation",{ if (args.search_type === "Tag" && args.tag) { return frappe.call({ type: "GET", - method: "frappe.desk.tags.get_tagged_docs", + method: "frappe.desk.doctype.tag.tag.get_tagged_docs", args: { "doctype": "Supplier", "tag": args.tag diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index a10ce46e33..95db33b0f8 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -344,13 +344,9 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc = @frappe.whitelist() def get_supplier_tag(): - data = frappe.db.sql("select _user_tags from `tabSupplier`") - - tags = [] - for tag in data: - tags += filter(bool, tag[0].split(",")) - - tags = list(set(tags)) - - return tags + if not frappe.cache().hget("Supplier", "Tags"): + filters = {"document_type": "Supplier"} + tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag])) + frappe.cache().hset("Supplier", "Tags", tags) + return frappe.cache().hget("Supplier", "Tags") From 73616d6b3366bd357c4df162349ac3ddff429232 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 14 Nov 2019 10:09:44 +0530 Subject: [PATCH 106/131] fix: duplication while bulk creation of item tax template (#19570) --- .../move_item_tax_to_item_tax_template.py | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 412f32030a..f25b9eaf52 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -1,20 +1,30 @@ import frappe import json from six import iteritems +from frappe.model.naming import make_autoname def execute(): if "tax_type" not in frappe.db.get_table_columns("Item Tax"): return old_item_taxes = {} item_tax_templates = {} - rename_template_to_untitled = [] + + frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1) + frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1) + existing_templates = frappe.db.sql("""select template.name, details.tax_type, details.tax_rate + from `tabItem Tax Template` template, `tabItem Tax Template Detail` details + where details.parent=template.name + """, as_dict=1) + + if len(existing_templates): + for d in existing_templates: + item_tax_templates.setdefault(d.name, {}) + item_tax_templates[d.name][d.tax_type] = d.tax_rate for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1): old_item_taxes.setdefault(d.item_code, []) old_item_taxes[d.item_code].append(d) - frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1) - frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1) frappe.reload_doc("stock", "doctype", "item", force=1) frappe.reload_doc("stock", "doctype", "item_tax", force=1) frappe.reload_doc("selling", "doctype", "quotation_item", force=1) @@ -27,6 +37,8 @@ def execute(): frappe.reload_doc("accounts", "doctype", "purchase_invoice_item", force=1) frappe.reload_doc("accounts", "doctype", "accounts_settings", force=1) + frappe.db.auto_commit_on_many_writes = True + # for each item that have item tax rates for item_code in old_item_taxes.keys(): # make current item's tax map @@ -34,8 +46,7 @@ def execute(): for d in old_item_taxes[item_code]: item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, rename_template_to_untitled, - item_tax_map, item_code) + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) # update the item tax table item = frappe.get_doc("Item", item_code) @@ -49,35 +60,33 @@ def execute(): 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice' ] + for dt in doctypes: for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` - where ifnull(item_tax_rate, '') not in ('', '{{}}')""".format(dt), as_dict=1): + where ifnull(item_tax_rate, '') not in ('', '{{}}') + and item_tax_template is NULL""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) - item_tax_template = get_item_tax_template(item_tax_templates, rename_template_to_untitled, + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, d.item_code, d.parent) - frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template) + frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) - idx = 1 - for oldname in rename_template_to_untitled: - frappe.rename_doc("Item Tax Template", oldname, "Untitled {}".format(idx)) - idx += 1 + frappe.db.auto_commit_on_many_writes = False settings = frappe.get_single("Accounts Settings") settings.add_taxes_from_item_tax_template = 0 settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, item_code, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: - if not parent: - rename_template_to_untitled.append(template) return template # if no item tax template found, create one item_tax_template = frappe.new_doc("Item Tax Template") - item_tax_template.title = "{}--{}".format(parent, item_code) if parent else "Item-{}".format(item_code) + item_tax_template.title = make_autoname("Item Tax Template-.####") + for tax_type, tax_rate in iteritems(item_tax_map): if not frappe.db.exists("Account", tax_type): parts = tax_type.strip().split(" - ") From 5503b6cff56d876b4a88046cb7810cf83155dea1 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 14 Nov 2019 10:11:25 +0530 Subject: [PATCH 107/131] fix(Task): Do not create/schedule task after project end (#19184) * fix: do not create/schedule task after project end * fix: check difference between dates * fix: check project date * fix: task creation * fix: tests --- .../doctype/crop_cycle/crop_cycle.py | 14 +++++------ erpnext/projects/doctype/task/task.py | 25 +++++++++++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py index bb9045ca81..3e51933df7 100644 --- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py +++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py @@ -51,27 +51,25 @@ class CropCycle(Document): self.create_task(disease_doc.treatment_task, self.name, start_date) def create_project(self, period, crop_tasks): - project = frappe.new_doc("Project") - project.update({ + project = frappe.get_doc({ + "doctype": "Project", "project_name": self.title, "expected_start_date": self.start_date, "expected_end_date": add_days(self.start_date, period - 1) - }) - project.insert() + }).insert() return project.name def create_task(self, crop_tasks, project_name, start_date): for crop_task in crop_tasks: - task = frappe.new_doc("Task") - task.update({ + frappe.get_doc({ + "doctype": "Task", "subject": crop_task.get("task_name"), "priority": crop_task.get("priority"), "project": project_name, "exp_start_date": add_days(start_date, crop_task.get("start_day") - 1), "exp_end_date": add_days(start_date, crop_task.get("end_day") - 1) - }) - task.insert() + }).insert() def reload_linked_analysis(self): linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis'] diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 90e9f05f22..54fce8d6db 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -10,6 +10,7 @@ from frappe import _, throw from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate from frappe.utils.nestedset import NestedSet from frappe.desk.form.assign_to import close_all_assignments, clear +from frappe.utils import date_diff class CircularReferenceError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass @@ -28,16 +29,29 @@ class Task(NestedSet): def validate(self): self.validate_dates() + self.validate_parent_project_dates() self.validate_progress() self.validate_status() self.update_depends_on() def validate_dates(self): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): - frappe.throw(_("'Expected Start Date' can not be greater than 'Expected End Date'")) + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ + frappe.bold("Expected End Date"))) if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): - frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ + frappe.bold("Actual End Date"))) + + def validate_parent_project_dates(self): + if not self.project or frappe.flags.in_test: + return + + expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date")) + + if expected_end_date: + validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual") def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Completed": @@ -255,3 +269,10 @@ def add_multiple_tasks(data, parent): def on_doctype_update(): frappe.db.add_index("Task", ["lft", "rgt"]) + +def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date): + if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: + frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) + + if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) \ No newline at end of file From fc3b924d4dd727431c82925bce6b2f9afd90d698 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 14 Nov 2019 12:02:10 +0530 Subject: [PATCH 108/131] fix: skip leave ledger entry creation for denied leaves (#19556) * fix: skip leave ledger entry creation for denied leave application * patch: remove incorrect leave ledger entries * fix: create reverse ledger entry before setting status to cancel --- .../leave_application/leave_application.py | 5 +++- erpnext/patches.txt | 4 +-- .../remove_denied_leaves_from_leave_ledger.py | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index e1e5e8001d..0e6630541c 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -55,11 +55,11 @@ class LeaveApplication(Document): self.reload() def on_cancel(self): + self.create_leave_ledger_entry(submit=False) self.status = "Cancelled" # notify leave applier about cancellation self.notify_employee() self.cancel_attendance() - self.create_leave_ledger_entry(submit=False) def validate_applicable_after(self): if self.leave_type: @@ -351,6 +351,9 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): + if self.status != 'Approved': + return + expiry_date = get_allocation_expiry(self.employee, self.leave_type, self.to_date, self.from_date) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0155b27820..9e4dc12e65 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -638,11 +638,11 @@ erpnext.patches.v12_0.add_variant_of_in_item_attribute_table erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account erpnext.patches.v12_0.create_default_energy_point_rules erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order -erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields -erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template \ No newline at end of file +erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template +erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger \ No newline at end of file diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py new file mode 100644 index 0000000000..24f0e7cb21 --- /dev/null +++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py @@ -0,0 +1,26 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import getdate, today + +def execute(): + ''' Delete leave ledger entry created + via leave applications with status != Approved ''' + if not frappe.db.a_row_exists("Leave Ledger Entry"): + return + + leave_application_list = get_denied_leave_application_list() + if leave_application_list: + delete_denied_leaves_from_leave_ledger_entry(leave_application_list) + +def get_denied_leave_application_list(): + return frappe.db.sql_list(''' Select name from `tabLeave Application` where status <> 'Approved' ''') + +def delete_denied_leaves_from_leave_ledger_entry(leave_application_list): + frappe.db.sql(''' Delete + FROM `tabLeave Ledger Entry` + WHERE + transaction_type = 'Leave Application' + AND transaction_name in {0} '''.format(tuple(leave_application_list))) #nosec \ No newline at end of file From d69d0e30467d94f81f230c0facaada552ac5507c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 14 Nov 2019 13:05:13 +0530 Subject: [PATCH 109/131] fix(patch): skip leave ledger entry creation for denied leaves (#19579) --- .../v12_0/remove_denied_leaves_from_leave_ledger.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py index 24f0e7cb21..7859606e5c 100644 --- a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py +++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py @@ -19,8 +19,10 @@ def get_denied_leave_application_list(): return frappe.db.sql_list(''' Select name from `tabLeave Application` where status <> 'Approved' ''') def delete_denied_leaves_from_leave_ledger_entry(leave_application_list): - frappe.db.sql(''' Delete - FROM `tabLeave Ledger Entry` - WHERE - transaction_type = 'Leave Application' - AND transaction_name in {0} '''.format(tuple(leave_application_list))) #nosec \ No newline at end of file + if leave_application_list: + frappe.db.sql(''' Delete + FROM `tabLeave Ledger Entry` + WHERE + transaction_type = 'Leave Application' + AND transaction_name in (%s) ''' % (', '.join(['%s'] * len(leave_application_list))), #nosec + tuple(leave_application_list)) \ No newline at end of file From ec082754b43906d95a5ccc68b5cded806efeab79 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 14 Nov 2019 13:28:24 +0530 Subject: [PATCH 110/131] fix: One serial no can be tagged in multiple invoices if used against different items (#19580) --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0ebca8bf51..fefd36a313 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -991,10 +991,8 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - if serial_no and frappe.db.exists('Serial No', serial_no): - sno = frappe.get_doc('Serial No', serial_no) - sno.sales_invoice = invoice - sno.db_update() + if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code: + frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice) def validate_serial_numbers(self): """ @@ -1040,8 +1038,9 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice") - if sales_invoice and self.name != sales_invoice: + sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no, + ["sales_invoice", "item_code"]) + if sales_invoice and item_code == item.item_code and self.name != sales_invoice: sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" From e942f998976fc21443187f9e071f4003a5f86605 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Nov 2019 15:26:18 +0530 Subject: [PATCH 111/131] Update work_order.js --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 15a33ca329..107c79b89b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -540,7 +540,7 @@ erpnext.work_order = { calculate_total_cost: function(frm) { let variable_cost = flt(frm.doc.actual_operating_cost) || flt(frm.doc.planned_operating_cost); - frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) + frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)); }, set_default_warehouse: function(frm) { From c9e8a1bf96e379aaa239cd67c6aefb4233e124aa Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 14 Nov 2019 16:13:43 +0530 Subject: [PATCH 112/131] fix: Account Balance and Stock Value out of sync error message (#19526) * fix: Account Balance and Stock Value out of sync error message Added 'Make Adjustment Entry' button and enhanced message * fix: Split message and changed routing for translation --- erpnext/accounts/general_ledger.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index d4dac72601..38f283c8d4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -163,9 +163,16 @@ def validate_account_for_perpetual_inventory(gl_map): .format(account), StockAccountInvalidTransaction) elif account_bal != stock_bal: - frappe.throw(_("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and linked warehouse ({3}). Please create adjustment Journal Entry for amount {4}.") - .format(account_bal, stock_bal, account, comma_and(warehouse_list), stock_bal - account_bal), - StockValueAndAccountBalanceOutOfSync) + error_reason = _("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and it's linked warehouses.").format( + account_bal, stock_bal, frappe.bold(account)) + error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(stock_bal - account_bal)) + button_text = _("Make Adjustment Entry") + + frappe.throw("""{0}

      {1}

      +
      + +
      """.format(error_reason, error_resolution, button_text), + StockValueAndAccountBalanceOutOfSync, title=_('Account Balance Out Of Sync')) def validate_cwip_accounts(gl_map): cwip_enabled = cint(frappe.get_cached_value("Company", From cf55c9c6da39c5e0fad8aa7bd7dbaa8337505179 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 14 Nov 2019 18:22:20 +0530 Subject: [PATCH 113/131] fix: stock reconciliation shwoing incorrect current serial no and qty --- .../doctype/stock_reconciliation/stock_reconciliation.py | 2 +- erpnext/stock/utils.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 98a8c59483..3683e60fd6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -456,7 +456,7 @@ def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time } serial_nos_list = [serial_no.get("name") - for serial_no in get_available_serial_nos(item_code, warehouse)] + for serial_no in get_available_serial_nos(args)] qty = len(serial_nos_list) serial_nos = '\n'.join(serial_nos_list) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index d7629176a5..2c6c95393b 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -293,9 +293,11 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto row, key, value = data row[key] = value -def get_available_serial_nos(item_code, warehouse): - return frappe.get_all("Serial No", filters = {'item_code': item_code, - 'warehouse': warehouse, 'delivery_document_no': ''}) or [] +def get_available_serial_nos(args): + return frappe.db.sql(""" SELECT name from `tabSerial No` + WHERE item_code = %(item_code)s and warehouse = %(warehouse)s + and timestamp(purchase_date, purchase_time) <= timestamp(%(posting_date)s, %(posting_time)s) + """, args, as_dict=1) def add_additional_uom_columns(columns, result, include_uom, conversion_factors): if not include_uom or not conversion_factors: From d545f6fb6ba01456711d408a3459ddabd7529067 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 14 Nov 2019 19:26:49 +0530 Subject: [PATCH 114/131] fix: fetch approver from employee --- erpnext/hr/doctype/department_approver/department_approver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 7bf9905d07..d6b66da081 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -21,7 +21,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): department_list = [] employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) if employee.leave_approver: - approver = frappe.db.get_value("User", leave_approver, ['name', 'first_name', 'last_name']) + approver = frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']) approvers.append(approver) return approvers From 74bbcb539f8303375edf6815e007017d95f8e98f Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 14 Nov 2019 22:44:15 +0530 Subject: [PATCH 115/131] fix: Ignore period closing voucher entries in accounts dashboard --- .../account_balance_timeline/account_balance_timeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 716bef381b..43acded3a9 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -93,7 +93,8 @@ def get_gl_entries(account, to_date): fields = ['posting_date', 'debit', 'credit'], filters = [ dict(posting_date = ('<', to_date)), - dict(account = ('in', child_accounts)) + dict(account = ('in', child_accounts)), + dict(voucher_type = ('!=', 'Period Closing Voucher')) ], order_by = 'posting_date asc') From 642441688687286ba0fce0df1b2319529c723bdd Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 15 Nov 2019 14:18:45 +0530 Subject: [PATCH 116/131] fix: sales order item shwoing incorrect produced qty (#19584) --- .../doctype/work_order/work_order.py | 4 +++- ...ed_qty_field_in_sales_order_for_work_order.py | 14 +++++++++----- .../selling/doctype/sales_order/sales_order.py | 16 ++++++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 6ea3dc83ed..089cb8014d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -216,7 +216,9 @@ class WorkOrder(Document): self.db_set(fieldname, qty) from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item - update_produced_qty_in_so_item(self.sales_order_item) + + if self.sales_order and self.sales_order_item: + update_produced_qty_in_so_item(self.sales_order, self.sales_order_item) if self.production_plan: self.update_production_plan_status() diff --git a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py index 44d8fa767a..07026732fd 100644 --- a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py +++ b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py @@ -3,8 +3,12 @@ from frappe.utils import flt from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item def execute(): - frappe.reload_doctype('Sales Order Item') - frappe.reload_doctype('Sales Order') - sales_order_items = frappe.db.get_all('Sales Order Item', ['name']) - for so_item in sales_order_items: - update_produced_qty_in_so_item(so_item.get('name')) \ No newline at end of file + frappe.reload_doctype('Sales Order Item') + frappe.reload_doctype('Sales Order') + + for d in frappe.get_all('Work Order', + fields = ['sales_order', 'sales_order_item'], + filters={'sales_order': ('!=', ''), 'sales_order_item': ('!=', '')}): + + # update produced qty in sales order + update_produced_qty_in_so_item(d.sales_order, d.sales_order_item) \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c4c3c0f81e..e12b359bdf 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1038,14 +1038,18 @@ def create_pick_list(source_name, target_doc=None): return doc -def update_produced_qty_in_so_item(sales_order_item): +def update_produced_qty_in_so_item(sales_order, sales_order_item): #for multiple work orders against same sales order item linked_wo_with_so_item = frappe.db.get_all('Work Order', ['produced_qty'], { 'sales_order_item': sales_order_item, + 'sales_order': sales_order, 'docstatus': 1 }) - if len(linked_wo_with_so_item) > 0: - total_produced_qty = 0 - for wo in linked_wo_with_so_item: - total_produced_qty += flt(wo.get('produced_qty')) - frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty) \ No newline at end of file + + total_produced_qty = 0 + for wo in linked_wo_with_so_item: + total_produced_qty += flt(wo.get('produced_qty')) + + if not total_produced_qty and frappe.flags.in_patch: return + + frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty) \ No newline at end of file From d995609ffa5e3903fecb3102f359992688e63401 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 18 Nov 2019 11:46:55 +0530 Subject: [PATCH 117/131] Fixed asset refactor (#19369) * refactor: Asset Movement with multiple assets using table * refactor: Create Asset Movement from Asset List & Linking PR/PI with Asset * feat: Auto create asset on Purchase checkbox * refactor: LCV for asset created via PR/PI * refactor: get asset category accounts from item master * refactor: Purchase Receipt for asset purchasing * refactor: Purchase Invoice for asset purchasing * fix: post-refactor delete fixes * refactor: moved asset validation from pr/pi on asset submission * fix: Asset Category should be defined for auto purchasing assets * fix: undo serial_no_update removal (for non asset item) from LCV * fix: remove duplicate calls from item.js * fix: args position of all occurrence of get_asset_category_account() * fix: test cases * fix: landed cost voucher validations * refactor: test case for auto creation of asset * fix: removed invalid assertions * fix: patch errors on travis * fix: codacy fixes * fix: PI Items not fetching details from item * fix: asset movement from list view having default purpose 'Receipt' * chore: msgprint for selecting item code first while creating asset manually * fix: alert messages * minor: asset movement fixes * fix: lcv was made against submitted assets * minor: ux fixes * refac: move specific asset validation to SINV * chore: remove make_purchase_invoice from asset form * make asset movement on asset submission * add PR & PI queries based on item code * refac: not allow last movement cancellation * move asset movement creation on asset submission * asset movement naming series * add tests * fix: code review changes * chore: remove unecessary asset movement updation * refac: setting latest location while making asset movements * Added extra validations * fix: form dashboard make lcv button * fix: auto asset movement creation validation * fix: allow lcv against other items after removing submitted assets * chore: remove unwanted condition * fix: mismatch debit credit on purchase of asset with valuation tax * chore: toggle required field based on movement type * chore: fix lcv error message * fix: travis failing * fix: travis failing test * fix: wrong conditions after merge * fix: cannot cancel assets * fix: travis failing* fix change in deletion of assets * fix: codacy * fix: process cancellation of assets * refac: cancellation of pr only deletes auto created assets * fix: incorrect query --- .../purchase_invoice/purchase_invoice.js | 22 +- .../purchase_invoice/purchase_invoice.py | 108 +- .../purchase_invoice_item.json | 30 +- .../doctype/sales_invoice/sales_invoice.py | 10 + erpnext/assets/doctype/asset/asset.js | 161 +- erpnext/assets/doctype/asset/asset.json | 11 +- erpnext/assets/doctype/asset/asset.py | 126 +- erpnext/assets/doctype/asset/asset_list.js | 19 +- erpnext/assets/doctype/asset/test_asset.py | 1030 ++++---- .../doctype/asset_category/asset_category.py | 7 +- .../test_asset_maintenance.py | 8 +- .../doctype/asset_movement/asset_movement.js | 145 +- .../asset_movement/asset_movement.json | 103 +- .../doctype/asset_movement/asset_movement.py | 179 +- .../asset_movement/test_asset_movement.py | 130 +- .../doctype/asset_movement_item/__init__.py | 0 .../asset_movement_item.json | 86 + .../asset_movement_item.py | 10 + .../purchase_order_item.json | 11 +- erpnext/controllers/accounts_controller.py | 42 - erpnext/controllers/buying_controller.py | 137 +- erpnext/controllers/queries.py | 26 + erpnext/hr/doctype/employee/test_employee.py | 6 +- ..._asset_finance_book_against_old_entries.py | 4 - erpnext/stock/doctype/item/item.js | 21 +- erpnext/stock/doctype/item/item.json | 2217 +++++++++-------- .../landed_cost_item/landed_cost_item.json | 16 +- .../landed_cost_voucher.js | 4 + .../landed_cost_voucher.json | 648 +---- .../landed_cost_voucher.py | 43 +- .../purchase_receipt/purchase_receipt.js | 32 +- .../purchase_receipt/purchase_receipt.py | 157 +- .../purchase_receipt/test_purchase_receipt.py | 32 +- .../purchase_receipt_item.json | 53 +- 34 files changed, 2817 insertions(+), 2817 deletions(-) create mode 100644 erpnext/assets/doctype/asset_movement_item/__init__.py create mode 100644 erpnext/assets/doctype/asset_movement_item/asset_movement_item.json create mode 100644 erpnext/assets/doctype/asset_movement_item/asset_movement_item.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index f4b656d3f6..e4e2c7b10f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -331,15 +331,15 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }) }, - asset: function(frm, cdt, cdn) { + item_code: function(frm, cdt, cdn) { var row = locals[cdt][cdn]; - if(row.asset) { + if(row.item_code) { frappe.call({ method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", args: { - "asset": row.asset, + "item": row.item_code, "fieldname": "fixed_asset_account", - "account": row.expense_account + "company": frm.doc.company }, callback: function(r, rt) { frappe.model.set_value(cdt, cdn, "expense_account", r.message); @@ -430,19 +430,7 @@ cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) cur_frm.set_query("expense_account", "items", function(doc) { return { query: "erpnext.controllers.queries.get_expense_account", - filters: {'company': doc.company} - } -}); - -cur_frm.set_query("asset", "items", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - 'item_code': d.item_code, - 'docstatus': 1, - 'company': doc.company, - 'status': 'Submitted' - } + filters: {'company': doc.company } } }); diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f1c490e2cd..4fbf9a1009 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -98,7 +98,6 @@ class PurchaseInvoice(BuyingController): self.set_against_expense_account() self.validate_write_off_account() self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") - self.validate_fixed_asset() self.create_remarks() self.set_status() self.validate_purchase_receipt_if_update_stock() @@ -238,13 +237,8 @@ class PurchaseInvoice(BuyingController): item.expense_account = warehouse_account[item.warehouse]["account"] else: item.expense_account = stock_not_billed_account - elif item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, asset_category): - if not item.asset: - frappe.throw(_("Row {0}: asset is required for item {1}") - .format(item.idx, item.item_code)) - - item.expense_account = get_asset_category_account(item.asset, 'fixed_asset_account', + item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code, company = self.company) elif item.is_fixed_asset and item.pr_detail: item.expense_account = asset_received_but_not_billed @@ -363,7 +357,7 @@ class PurchaseInvoice(BuyingController): return if not gl_entries: gl_entries = self.get_gl_entries() - + if gl_entries: update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" @@ -510,19 +504,49 @@ class PurchaseInvoice(BuyingController): asset_category)): expense_account = (item.expense_account if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) + + if not item.is_fixed_asset: + amount = flt(item.base_net_amount, item.precision("base_net_amount")) + else: + amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) - gl_entries.append( - self.get_gl_dict({ + gl_entries.append(self.get_gl_dict({ "account": expense_account, "against": self.supplier, - "debit": flt(item.base_net_amount, item.precision("base_net_amount")), - "debit_in_account_currency": (flt(item.base_net_amount, - item.precision("base_net_amount")) if account_currency==self.company_currency - else flt(item.net_amount, item.precision("net_amount"))), + "debit": amount, "cost_center": item.cost_center, "project": item.project - }, account_currency, item=item) - ) + }, account_currency, item=item)) + + # If asset is bought through this document and not linked to PR + if self.update_stock and item.landed_cost_voucher_amount: + expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") + # Amount added through landed-cost-voucher + gl_entries.append(self.get_gl_dict({ + "account": expenses_included_in_asset_valuation, + "against": expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + gl_entries.append(self.get_gl_dict({ + "account": expense_account, + "against": expenses_included_in_asset_valuation, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + # update gross amount of asset bought through this document + assets = frappe.db.get_all('Asset', + filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) if self.auto_accounting_for_stock and self.is_opening == "No" and \ item.item_code in stock_items and item.item_tax_amount: @@ -547,30 +571,27 @@ class PurchaseInvoice(BuyingController): item.precision("item_tax_amount")) def get_asset_gl_entry(self, gl_entries): + arbnb_account = self.get_company_default("asset_received_but_not_billed") + eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") + for item in self.get("items"): - if item.item_code and item.is_fixed_asset : - asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - - if item.is_fixed_asset and is_cwip_accounting_enabled(self.company, asset_category) : - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - + if item.is_fixed_asset: asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) - if (not item.expense_account or frappe.db.get_value('Account', - item.expense_account, 'account_type') not in ['Asset Received But Not Billed', 'Fixed Asset']): - arbnb_account = self.get_company_default("asset_received_but_not_billed") + item_exp_acc_type = frappe.db.get_value('Account', item.expense_account, 'account_type') + if (not item.expense_account or item_exp_acc_type not in ['Asset Received But Not Billed', 'Fixed Asset']): item.expense_account = arbnb_account if not self.update_stock: - asset_rbnb_currency = get_account_currency(item.expense_account) + arbnb_currency = get_account_currency(item.expense_account) gl_entries.append(self.get_gl_dict({ "account": item.expense_account, "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount - if asset_rbnb_currency == self.company_currency else asset_amount), + if arbnb_currency == self.company_currency else asset_amount), "cost_center": item.cost_center }, item=item)) @@ -587,8 +608,7 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount / self.conversion_rate) }, item=item)) else: - cwip_account = get_asset_account("capital_work_in_progress_account", - item.asset, company = self.company) + cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) cwip_account_currency = get_account_currency(cwip_account) gl_entries.append(self.get_gl_dict({ @@ -613,6 +633,36 @@ class PurchaseInvoice(BuyingController): if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) }, item=item)) + + # When update stock is checked + # Assets are bought through this document then it will be linked to this document + if self.update_stock: + if flt(item.landed_cost_voucher_amount): + gl_entries.append(self.get_gl_dict({ + "account": eiiav_account, + "against": cwip_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + gl_entries.append(self.get_gl_dict({ + "account": cwip_account, + "against": eiiav_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + # update gross amount of assets bought through this document + assets = frappe.db.get_all('Asset', + filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) return gl_entries diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 3a19bb1b6b..dc3a1be0c7 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -71,8 +71,8 @@ "expense_account", "col_break5", "is_fixed_asset", - "asset", "asset_location", + "asset_category", "deferred_expense_section", "deferred_expense_account", "service_stop_date", @@ -116,6 +116,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.item_name", "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, @@ -191,6 +192,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.stock_uom", "fieldname": "uom", "fieldtype": "Link", "label": "UOM", @@ -414,6 +416,7 @@ "print_hide": 1 }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "batch_no", "fieldtype": "Link", "label": "Batch No", @@ -425,12 +428,14 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "serial_no", "fieldtype": "Text", "label": "Serial No", "no_copy": 1 }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "rejected_serial_no", "fieldtype": "Text", "label": "Rejected Serial No", @@ -615,6 +620,7 @@ }, { "default": "0", + "fetch_from": "item_code.is_fixed_asset", "fieldname": "is_fixed_asset", "fieldtype": "Check", "hidden": 1, @@ -623,14 +629,6 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset", - "fieldtype": "Link", - "label": "Asset", - "no_copy": 1, - "options": "Asset" - }, { "depends_on": "is_fixed_asset", "fieldname": "asset_location", @@ -676,7 +674,7 @@ "fieldname": "pr_detail", "fieldtype": "Data", "hidden": 1, - "label": "PR Detail", + "label": "Purchase Receipt Detail", "no_copy": 1, "oldfieldname": "pr_detail", "oldfieldtype": "Data", @@ -754,11 +752,21 @@ "fieldtype": "Data", "label": "Manufacturer Part Number", "read_only": 1 + }, + { + "depends_on": "is_fixed_asset", + "fetch_from": "item_code.asset_category", + "fieldname": "asset_category", + "fieldtype": "Data", + "in_preview": 1, + "label": "Asset Category", + "options": "Asset Category", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-09-17 22:32:05.984240", + "modified": "2019-11-03 13:43:23.782877", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fefd36a313..3d96d487a8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -135,7 +135,17 @@ class SalesInvoice(SellingController): if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: validate_loyalty_points(self, self.loyalty_points) + + def validate_fixed_asset(self): + for d in self.get("items"): + if d.is_fixed_asset and d.meta.get_field("asset") and d.asset: + asset = frappe.get_doc("Asset", d.asset) + if self.doctype == "Sales Invoice" and self.docstatus == 1: + if self.update_stock: + frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) + elif asset.status in ("Scrapped", "Cancelled", "Sold"): + frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status)) def before_save(self): set_account_for_mode_of_payment(self) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index c7390a2ef1..f0889bfa1b 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -41,6 +41,21 @@ frappe.ui.form.on('Asset', { }); }, + setup: function(frm) { + frm.set_query("purchase_receipt", (doc) => { + return { + query: "erpnext.controllers.queries.get_purchase_receipts", + filters: { item_code: doc.item_code } + } + }); + frm.set_query("purchase_invoice", (doc) => { + return { + query: "erpnext.controllers.queries.get_purchase_invoices", + filters: { item_code: doc.item_code } + } + }); + }, + refresh: function(frm) { frappe.ui.form.trigger("Asset", "is_existing_asset"); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); @@ -78,11 +93,6 @@ frappe.ui.form.on('Asset', { }); } - 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"); - }, __('Create')); - } if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) { frm.add_custom_button(__("Asset Maintenance"), function() { frm.trigger("create_asset_maintenance"); @@ -104,11 +114,36 @@ frappe.ui.form.on('Asset', { frm.trigger("setup_chart"); } + frm.trigger("toggle_reference_doc"); + if (frm.doc.docstatus == 0) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); } }, + toggle_reference_doc: function(frm) { + if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) { + frm.set_df_property('purchase_invoice', 'read_only', 1); + frm.set_df_property('purchase_receipt', 'read_only', 1); + } + else if (frm.doc.purchase_receipt) { + // if purchase receipt link is set then set PI disabled + frm.toggle_reqd('purchase_invoice', 0); + frm.set_df_property('purchase_invoice', 'read_only', 1); + } + else if (frm.doc.purchase_invoice) { + // if purchase invoice link is set then set PR disabled + frm.toggle_reqd('purchase_receipt', 0); + frm.set_df_property('purchase_receipt', 'read_only', 1); + } + else { + frm.toggle_reqd('purchase_receipt', 1); + frm.set_df_property('purchase_receipt', 'read_only', 0); + frm.toggle_reqd('purchase_invoice', 1); + frm.set_df_property('purchase_invoice', 'read_only', 0); + } + }, + make_journal_entry: function(frm) { frappe.call({ method: "erpnext.assets.doctype.asset.asset.make_journal_entry", @@ -176,21 +211,25 @@ frappe.ui.form.on('Asset', { 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, - asset_category: frm.doc.asset_category - }, - callback: function(r, rt) { - if(r.message) { - frm.set_value('finance_books', r.message); - } - } - }) + frm.trigger('set_finance_book'); } }, + set_finance_book: function(frm) { + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.get_item_details", + args: { + item_code: frm.doc.item_code, + asset_category: frm.doc.asset_category + }, + callback: function(r, rt) { + if(r.message) { + frm.set_value('finance_books', r.message); + } + } + }) + }, + available_for_use_date: function(frm) { $.each(frm.doc.finance_books || [], function(i, d) { if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date; @@ -207,29 +246,14 @@ frappe.ui.form.on('Asset', { }, make_schedules_editable: function(frm) { - var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 - ? true : false; + if (frm.doc.finance_books) { + var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 + ? 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); - } - }) + 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_sales_invoice: function(frm) { @@ -291,6 +315,65 @@ frappe.ui.form.on('Asset', { }) }, + purchase_receipt: function(frm) { + frm.trigger('toggle_reference_doc'); + + if (frm.doc.purchase_receipt) { + if (frm.doc.item_code) { + frappe.db.get_doc('Purchase Receipt', frm.doc.purchase_receipt).then(pr_doc => { + frm.set_value('company', pr_doc.company); + frm.set_value('purchase_date', pr_doc.posting_date); + const item = pr_doc.items.find(item => item.item_code === frm.doc.item_code); + if (!item) { + frm.set_value('purchase_receipt', ''); + frappe.msgprint({ + title: __('Invalid Purchase Receipt'), + message: __("The selected Purchase Receipt doesn't contains selected Asset Item."), + indicator: 'red' + }); + } + frm.set_value('gross_purchase_amount', item.base_net_rate); + frm.set_value('location', item.asset_location); + }); + } else { + frm.set_value('purchase_receipt', ''); + frappe.msgprint({ + title: __('Not Allowed'), + message: __("Please select Item Code first") + }); + } + } + }, + + purchase_invoice: function(frm) { + frm.trigger('toggle_reference_doc'); + if (frm.doc.purchase_invoice) { + if (frm.doc.item_code) { + frappe.db.get_doc('Purchase Invoice', frm.doc.purchase_invoice).then(pi_doc => { + frm.set_value('company', pi_doc.company); + frm.set_value('purchase_date', pi_doc.posting_date); + const item = pi_doc.items.find(item => item.item_code === frm.doc.item_code); + if (!item) { + frm.set_value('purchase_invoice', ''); + frappe.msgprint({ + title: __('Invalid Purchase Invoice'), + message: __("The selected Purchase Invoice doesn't contains selected Asset Item."), + indicator: 'red' + }); + } + frm.set_value('gross_purchase_amount', item.base_net_rate); + frm.set_value('location', item.asset_location); + }); + } else { + frm.set_value('purchase_invoice', ''); + frappe.msgprint({ + title: __('Not Allowed'), + message: __("Please select Item Code first") + }); + } + } + }, + set_depreciation_rate: function(frm, row) { if (row.total_number_of_depreciations && row.frequency_of_depreciation && row.expected_value_after_useful_life) { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 6882f6a992..97165a31d2 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -17,6 +17,7 @@ "supplier", "customer", "image", + "purchase_invoice", "column_break_3", "company", "location", @@ -25,6 +26,7 @@ "purchase_date", "disposal_date", "journal_entry_for_scrap", + "purchase_receipt", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -62,9 +64,8 @@ "status", "booked_fixed_asset", "column_break_51", - "purchase_receipt", + "purchase_receipt_amount", - "purchase_invoice", "default_finance_book", "amended_from" ], @@ -403,8 +404,7 @@ "label": "Purchase Receipt", "no_copy": 1, "options": "Purchase Receipt", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "purchase_receipt_amount", @@ -420,8 +420,7 @@ "fieldtype": "Link", "label": "Purchase Invoice", "no_copy": 1, - "options": "Purchase Invoice", - "read_only": 1 + "options": "Purchase Invoice" }, { "fetch_from": "company.default_finance_book", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index d1f8c1a8d3..9415eedc5c 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import AccountsController class Asset(AccountsController): def validate(self): self.validate_asset_values() + self.validate_asset_and_reference() self.validate_item() self.set_missing_values() self.prepare_depreciation_data() @@ -29,10 +30,13 @@ class Asset(AccountsController): def on_submit(self): self.validate_in_use_date() self.set_status() - self.update_stock_movement() + self.make_asset_movement() if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.company, self.asset_category): self.make_gl_entries() + + def before_cancel(self): + self.cancel_auto_gen_movement() def on_cancel(self): self.validate_cancellation() @@ -40,6 +44,18 @@ class Asset(AccountsController): self.set_status() delete_gl_entries(voucher_type='Asset', voucher_no=self.name) self.db_set('booked_fixed_asset', 0) + + def validate_asset_and_reference(self): + if self.purchase_invoice or self.purchase_receipt: + reference_doc = 'Purchase Invoice' if self.purchase_invoice else 'Purchase Receipt' + reference_name = self.purchase_invoice or self.purchase_receipt + reference_doc = frappe.get_doc(reference_doc, reference_name) + if reference_doc.get('company') != self.company: + frappe.throw(_("Company of asset {0} and purchase document {1} doesn't matches.").format(self.name, reference_doc.get('name'))) + + + if self.is_existing_asset and self.purchase_invoice: + frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)) def prepare_depreciation_data(self): if self.calculate_depreciation: @@ -109,6 +125,36 @@ class Asset(AccountsController): if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): frappe.throw(_("Available-for-use Date should be after purchase date")) + def cancel_auto_gen_movement(self): + reference_docname = self.purchase_invoice or self.purchase_receipt + movement = frappe.db.get_all('Asset Movement', filters={ 'reference_name': reference_docname, 'docstatus': 1 }) + if len(movement) > 1: + frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \ + cancelled manually to cancel this asset.')) + movement = frappe.get_doc('Asset Movement', movement[0].get('name')) + movement.flags.ignore_validate = True + movement.cancel() + + def make_asset_movement(self): + reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice' + reference_docname = self.purchase_receipt or self.purchase_invoice + assets = [{ + 'asset': self.name, + 'asset_name': self.asset_name, + 'target_location': self.location, + 'to_employee': self.custodian + }] + asset_movement = frappe.get_doc({ + 'doctype': 'Asset Movement', + 'assets': assets, + 'purpose': 'Receipt', + 'company': self.company, + 'transaction_date': getdate(nowdate()), + 'reference_doctype': reference_doctype, + 'reference_name': reference_docname + }).insert() + asset_movement.submit() + def set_depreciation_rate(self): for d in self.get("finance_books"): d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True), @@ -398,22 +444,13 @@ class Asset(AccountsController): if d.finance_book == self.default_finance_book: return cint(d.idx) - 1 - def update_stock_movement(self): - asset_movement = frappe.db.get_value('Asset Movement', - {'asset': self.name, 'reference_name': self.purchase_receipt, 'docstatus': 0}, 'name') - - if asset_movement: - doc = frappe.get_doc('Asset Movement', asset_movement) - doc.naming_series = 'ACC-ASM-.YYYY.-' - doc.submit() - def make_gl_entries(self): gl_entries = [] - if ((self.purchase_receipt or (self.purchase_invoice and - frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) + if ((self.purchase_receipt \ + or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): - fixed_aseet_account = get_asset_category_account(self.name, 'fixed_asset_account', + fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, asset_category = self.asset_category, company = self.company) cwip_account = get_asset_account("capital_work_in_progress_account", @@ -421,7 +458,7 @@ class Asset(AccountsController): gl_entries.append(self.get_gl_dict({ "account": cwip_account, - "against": fixed_aseet_account, + "against": fixed_asset_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, "credit": self.purchase_receipt_amount, @@ -430,7 +467,7 @@ class Asset(AccountsController): })) gl_entries.append(self.get_gl_dict({ - "account": fixed_aseet_account, + "account": fixed_asset_account, "against": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, @@ -491,25 +528,6 @@ def get_asset_naming_series(): meta = frappe.get_meta('Asset') return meta.get_field("naming_series").options -@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.get_cached_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_asset_category_account(asset, 'fixed_asset_account'), - "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, serial_no=None): si = frappe.new_doc("Sales Invoice") @@ -584,7 +602,7 @@ def get_item_details(item_code, asset_category): def get_asset_account(account_name, asset=None, asset_category=None, company=None): account = None if asset: - account = get_asset_category_account(asset, account_name, + account = get_asset_category_account(account_name, asset=asset, asset_category = asset_category, company = company) if not account: @@ -627,6 +645,44 @@ def make_journal_entry(asset_name): return je +@frappe.whitelist() +def make_asset_movement(assets): + import json + from six import string_types + + if isinstance(assets, string_types): + assets = json.loads(assets) + + if len(assets) == 0: + frappe.throw(_('Atleast one asset has to be selected.')) + + asset_movement = frappe.new_doc("Asset Movement") + asset_movement.quantity = len(assets) + prev_reference_docname = '' + + for asset in assets: + asset = frappe.get_doc('Asset', asset.get('name')) + # get PR/PI linked with asset + reference_docname = asset.get('purchase_receipt') if asset.get('purchase_receipt') \ + else asset.get('purchase_invoice') + # checks if all the assets are linked with a single PR/PI + if prev_reference_docname == '': + prev_reference_docname = reference_docname + elif prev_reference_docname != reference_docname: + frappe.throw(_('Assets selected should belong to same reference document.')) + + asset_movement.company = asset.get('company') + asset_movement.reference_doctype = 'Purchase Receipt' if asset.get('purchase_receipt') else 'Purchase Invoice' + asset_movement.reference_name = prev_reference_docname + asset_movement.append("assets", { + 'asset': asset.get('name'), + 'source_location': asset.get('location'), + 'from_employee': asset.get('custodian') + }) + + if asset_movement.get('assets'): + return asset_movement.as_dict() + def is_cwip_accounting_enabled(company, asset_category=None): enable_cwip_in_company = cint(frappe.db.get_value("Company", company, "enable_cwip_accounting")) diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js index 3b95a17afc..46cde6ee81 100644 --- a/erpnext/assets/doctype/asset/asset_list.js +++ b/erpnext/assets/doctype/asset/asset_list.js @@ -30,8 +30,23 @@ frappe.listview_settings['Asset'] = { } else if (doc.status === "Draft") { return [__("Draft"), "red", "status,=,Draft"]; - } - + }, + onload: function(me) { + me.page.add_action_item('Make Asset Movement', function() { + const assets = me.get_checked_items(); + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.make_asset_movement", + args:{ + "assets": assets + }, + callback: function (r) { + if (r.message) { + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }); }, } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7085b31e05..53fd6d394d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -7,7 +7,7 @@ import frappe import unittest from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, add_months 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 +from erpnext.assets.doctype.asset.asset import make_sales_invoice from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice @@ -39,15 +39,15 @@ class TestAsset(unittest.TestCase): }) asset.submit() - pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, - asset.company, asset.purchase_date) + pi = make_invoice(pr.name) pi.supplier = "_Test Supplier" pi.insert() pi.submit() asset.load_from_db() self.assertEqual(asset.supplier, "_Test Supplier") self.assertEqual(asset.purchase_date, getdate(purchase_date)) - self.assertEqual(asset.purchase_invoice, pi.name) + # Asset won't have reference to PI when purchased through PR + self.assertEqual(asset.purchase_receipt, pr.name) expected_gle = ( ("Asset Received But Not Billed - _TC", 100000.0, 0.0), @@ -60,521 +60,517 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) pi.cancel() - + asset.cancel() asset.load_from_db() - self.assertEqual(asset.supplier, None) - self.assertEqual(asset.purchase_invoice, None) + pr.load_from_db() + pr.cancel() + self.assertEqual(asset.docstatus, 2) self.assertFalse(frappe.db.get_value("GL Entry", {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) - def test_is_fixed_asset_set(self): - asset = create_asset(is_existing_asset = 1) - doc = frappe.new_doc('Purchase Invoice') - doc.supplier = '_Test Supplier' - doc.append('items', { - 'item_code': 'Macbook Pro', - 'qty': 1, - 'asset': asset.name - }) - - doc.set_missing_values() - self.assertEquals(doc.items[0].is_fixed_asset, 1) - - - def test_schedule_for_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() - - self.assertEqual(asset.status, "Draft") - expected_schedules = [ - ["2030-12-31", 30000.00, 30000.00], - ["2031-12-31", 30000.00, 60000.00], - ["2032-12-31", 30000.00, 90000.00] - ] - - 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): - create_asset(is_existing_asset=1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 40000 - asset.available_for_use_date = "2030-06-06" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.insert() - self.assertEqual(asset.status, "Draft") - asset.save() - expected_schedules = [ - ["2030-12-31", 14246.58, 54246.58], - ["2031-12-31", 25000.00, 79246.58], - ["2032-06-06", 10753.42, 90000.00] - ] - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": '2030-12-31' - }) - asset.insert() - self.assertEqual(asset.status, "Draft") - asset.save() - - expected_schedules = [ - ['2030-12-31', 66667.00, 66667.00], - ['2031-12-31', 22222.11, 88889.11], - ['2032-12-31', 1110.89, 90000.0] - ] - - 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): - create_asset(is_existing_asset = 1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.is_existing_asset = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 50000 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2029-11-30' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.insert() - self.assertEqual(asset.status, "Draft") - - expected_schedules = [ - ["2030-12-31", 33333.50, 83333.50], - ["2031-12-31", 6666.50, 90000.0] - ] - - 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_prorated_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2030-01-30' - asset.is_existing_asset = 0 - asset.available_for_use_date = "2030-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - - asset.insert() - asset.save() - - expected_schedules = [ - ["2030-12-31", 27534.25, 27534.25], - ["2031-12-31", 30000.0, 57534.25], - ["2032-12-31", 30000.0, 87534.25], - ["2033-01-30", 2465.75, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' - asset.available_for_use_date = "2020-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.insert() - 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() - - # 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"), 0) - - def test_depreciation_entry_for_wdv_without_pro_rata(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 4000.00, 4000.00], - ["2031-12-31", 2000.00, 6000.00], - ["2032-12-31", 1000.00, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_pro_rata_depreciation_entry_for_wdv(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 2279.45, 2279.45], - ["2031-12-31", 2860.28, 5139.73], - ["2032-12-31", 1430.14, 6569.87], - ["2033-06-06", 430.13, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation_entry_cancellation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.insert() - 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): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = nowdate() - asset.purchase_date = nowdate() - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": nowdate() - }) - asset.insert() - asset.submit() - - post_depreciation_entries(date=add_months(nowdate(), 10)) - - scrap_asset(asset.name) - - 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(asset.name) - - asset.load_from_db() - self.assertFalse(asset.journal_entry_for_scrap) - self.assertEqual(asset.status, "Partially Depreciated") - - def test_asset_sale(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.insert() - asset.submit() - post_depreciation_entries(date="2021-01-01") - - si = make_sales_invoice(asset=asset.name, 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", asset.name, "status"), "Sold") - - expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), - ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 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", asset.name, "status"), "Partially Depreciated") - - def test_asset_expected_value_after_useful_life(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" - }) - asset.insert() - 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.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) - - def test_cwip_accounting(self): - from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - make_purchase_invoice as make_purchase_invoice_from_pr) - - #frappe.db.set_value("Asset Category","Computers","enable_cwip_accounting", 1) - - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=5000, do_not_submit=True, location="Test Location") - - pr.set('taxes', [{ - 'category': 'Total', - 'add_deduct_tax': 'Add', - 'charge_type': 'On Net Total', - 'account_head': '_Test Account Service Tax - _TC', - 'description': '_Test Account Service Tax', - 'cost_center': 'Main - _TC', - 'rate': 5.0 - }, { - 'category': 'Valuation and Total', - 'add_deduct_tax': 'Add', - 'charge_type': 'On Net Total', - 'account_head': '_Test Account Shipping Charges - _TC', - 'description': '_Test Account Shipping Charges', - 'cost_center': 'Main - _TC', - 'rate': 5.0 - }]) - - pr.submit() - - expected_gle = ( - ("Asset Received But Not Billed - _TC", 0.0, 5250.0), - ("CWIP Account - _TC", 5250.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no = %s - order by account""", pr.name) - - self.assertEqual(gle, expected_gle) - - pi = make_purchase_invoice_from_pr(pr.name) - pi.submit() - - expected_gle = ( - ("_Test Account Service Tax - _TC", 250.0, 0.0), - ("_Test Account Shipping Charges - _TC", 250.0, 0.0), - ("Asset Received But Not Billed - _TC", 5250.0, 0.0), - ("Creditors - _TC", 0.0, 5500.0), - ("Expenses Included In Asset Valuation - _TC", 0.0, 250.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) - - asset = frappe.db.get_value('Asset', - {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') - - asset_doc = frappe.get_doc('Asset', asset) - - month_end_date = get_last_day(nowdate()) - asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) - - asset_doc.append("finance_books", { - "expected_value_after_useful_life": 200, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": month_end_date - }) - asset_doc.submit() - - expected_gle = ( - ("_Test Fixed Asset - _TC", 5250.0, 0.0), - ("CWIP Account - _TC", 0.0, 5250.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Asset' and voucher_no = %s - order by account""", asset_doc.name) - - - self.assertEqual(gle, expected_gle) - - def test_expense_head(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=2, rate=200000.0, location="Test Location") - - doc = make_invoice(pr.name) - - self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + # def test_is_fixed_asset_set(self): + # asset = create_asset(is_existing_asset = 1) + # doc = frappe.new_doc('Purchase Invoice') + # doc.supplier = '_Test Supplier' + # doc.append('items', { + # 'item_code': 'Macbook Pro', + # 'qty': 1, + # 'asset': asset.name + # }) + + # doc.set_missing_values() + # self.assertEquals(doc.items[0].is_fixed_asset, 1) + + + # def test_schedule_for_straight_line_method(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2030-01-01' + + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.save() + + # self.assertEqual(asset.status, "Draft") + # expected_schedules = [ + # ["2030-12-31", 30000.00, 30000.00], + # ["2031-12-31", 30000.00, 60000.00], + # ["2032-12-31", 30000.00, 90000.00] + # ] + + # 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): + # create_asset(is_existing_asset=1) + # asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + # asset.calculate_depreciation = 1 + # asset.number_of_depreciations_booked = 1 + # asset.opening_accumulated_depreciation = 40000 + # asset.available_for_use_date = "2030-06-06" + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.insert() + # self.assertEqual(asset.status, "Draft") + # asset.save() + # expected_schedules = [ + # ["2030-12-31", 14246.58, 54246.58], + # ["2031-12-31", 25000.00, 79246.58], + # ["2032-06-06", 10753.42, 90000.00] + # ] + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_schedule_for_double_declining_method(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2030-01-01' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Double Declining Balance", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": '2030-12-31' + # }) + # asset.insert() + # self.assertEqual(asset.status, "Draft") + # asset.save() + + # expected_schedules = [ + # ['2030-12-31', 66667.00, 66667.00], + # ['2031-12-31', 22222.11, 88889.11], + # ['2032-12-31', 1110.89, 90000.0] + # ] + + # 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): + # create_asset(is_existing_asset = 1) + # asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + # asset.calculate_depreciation = 1 + # asset.is_existing_asset = 1 + # asset.number_of_depreciations_booked = 1 + # asset.opening_accumulated_depreciation = 50000 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2029-11-30' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Double Declining Balance", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.insert() + # self.assertEqual(asset.status, "Draft") + + # expected_schedules = [ + # ["2030-12-31", 33333.50, 83333.50], + # ["2031-12-31", 6666.50, 90000.0] + # ] + + # 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_prorated_straight_line_method(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.purchase_date = '2030-01-30' + # asset.is_existing_asset = 0 + # asset.available_for_use_date = "2030-01-30" + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + + # asset.insert() + # asset.save() + + # expected_schedules = [ + # ["2030-12-31", 27534.25, 27534.25], + # ["2031-12-31", 30000.0, 57534.25], + # ["2032-12-31", 30000.0, 87534.25], + # ["2033-01-30", 2465.75, 90000.0] + # ] + + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_depreciation(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.purchase_date = '2020-01-30' + # asset.available_for_use_date = "2020-01-30" + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-12-31" + # }) + # asset.insert() + # 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() + + # # 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"), 0) + + # def test_depreciation_entry_for_wdv_without_pro_rata(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=8000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2030-01-01' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 1000, + # "depreciation_method": "Written Down Value", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.save(ignore_permissions=True) + + # self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + # expected_schedules = [ + # ["2030-12-31", 4000.00, 4000.00], + # ["2031-12-31", 2000.00, 6000.00], + # ["2032-12-31", 1000.00, 7000.0], + # ] + + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_pro_rata_depreciation_entry_for_wdv(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=8000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-06-06' + # asset.purchase_date = '2030-01-01' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 1000, + # "depreciation_method": "Written Down Value", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.save(ignore_permissions=True) + + # self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + # expected_schedules = [ + # ["2030-12-31", 2279.45, 2279.45], + # ["2031-12-31", 2860.28, 5139.73], + # ["2032-12-31", 1430.14, 6569.87], + # ["2033-06-06", 430.13, 7000.0], + # ] + + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_depreciation_entry_cancellation(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2020-06-06' + # asset.purchase_date = '2020-06-06' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-12-31" + # }) + # asset.insert() + # 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): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = nowdate() + # asset.purchase_date = nowdate() + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": nowdate() + # }) + # asset.insert() + # asset.submit() + + # post_depreciation_entries(date=add_months(nowdate(), 10)) + + # scrap_asset(asset.name) + + # 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(asset.name) + + # asset.load_from_db() + # self.assertFalse(asset.journal_entry_for_scrap) + # self.assertEqual(asset.status, "Partially Depreciated") + + # def test_asset_sale(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2020-06-06' + # asset.purchase_date = '2020-06-06' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-12-31" + # }) + # asset.insert() + # asset.submit() + # post_depreciation_entries(date="2021-01-01") + + # si = make_sales_invoice(asset=asset.name, 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", asset.name, "status"), "Sold") + + # expected_gle = ( + # ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), + # ("_Test Fixed Asset - _TC", 0.0, 100000.0), + # ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 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", asset.name, "status"), "Partially Depreciated") + + # def test_asset_expected_value_after_useful_life(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2020-06-06' + # asset.purchase_date = '2020-06-06' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-06-06" + # }) + # asset.insert() + # 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.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + + # def test_cwip_accounting(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=5000, do_not_submit=True, location="Test Location") + + # pr.set('taxes', [{ + # 'category': 'Total', + # 'add_deduct_tax': 'Add', + # 'charge_type': 'On Net Total', + # 'account_head': '_Test Account Service Tax - _TC', + # 'description': '_Test Account Service Tax', + # 'cost_center': 'Main - _TC', + # 'rate': 5.0 + # }, { + # 'category': 'Valuation and Total', + # 'add_deduct_tax': 'Add', + # 'charge_type': 'On Net Total', + # 'account_head': '_Test Account Shipping Charges - _TC', + # 'description': '_Test Account Shipping Charges', + # 'cost_center': 'Main - _TC', + # 'rate': 5.0 + # }]) + + # pr.submit() + + # expected_gle = ( + # ("Asset Received But Not Billed - _TC", 0.0, 5250.0), + # ("CWIP Account - _TC", 5250.0, 0.0) + # ) + + # pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Purchase Receipt' and voucher_no = %s + # order by account""", pr.name) + + # self.assertEqual(pr_gle, expected_gle) + + # pi = make_invoice(pr.name) + # pi.submit() + + # expected_gle = ( + # ("_Test Account Service Tax - _TC", 250.0, 0.0), + # ("_Test Account Shipping Charges - _TC", 250.0, 0.0), + # ("Asset Received But Not Billed - _TC", 5250.0, 0.0), + # ("Creditors - _TC", 0.0, 5500.0), + # ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), + # ) + + # pi_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(pi_gle, expected_gle) + + # asset = frappe.db.get_value('Asset', + # {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') + + # asset_doc = frappe.get_doc('Asset', asset) + + # month_end_date = get_last_day(nowdate()) + # asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + # self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) + + # asset_doc.append("finance_books", { + # "expected_value_after_useful_life": 200, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": month_end_date + # }) + # asset_doc.submit() + + # expected_gle = ( + # ("_Test Fixed Asset - _TC", 5250.0, 0.0), + # ("CWIP Account - _TC", 0.0, 5250.0) + # ) + + # gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Asset' and voucher_no = %s + # order by account""", asset_doc.name) + + + # self.assertEqual(gle, expected_gle) + + # def test_expense_head(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=2, rate=200000.0, location="Test Location") + + # doc = make_invoice(pr.name) + + # self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): @@ -636,6 +632,8 @@ def create_asset_category(): asset_category.insert() def create_fixed_asset_item(): + meta = frappe.get_meta('Asset') + naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-' try: frappe.get_doc({ "doctype": "Item", @@ -646,7 +644,9 @@ def create_fixed_asset_item(): "item_group": "All Item Groups", "stock_uom": "Nos", "is_stock_item": 0, - "is_fixed_asset": 1 + "is_fixed_asset": 1, + "auto_create_assets": 1, + "asset_naming_series": naming_series }).insert() except frappe.DuplicateEntryError: pass diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 5cb634abcd..14f3922c05 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -29,8 +29,11 @@ class AssetCategory(Document): frappe.bold(d.idx), frappe.bold(d.company_name))) @frappe.whitelist() -def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None): - if not asset_category and company: +def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None): + if item and frappe.db.get_value("Item", item, "is_fixed_asset"): + asset_category = frappe.db.get_value("Item", item, ["asset_category"]) + + elif not asset_category or not company: if account: if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": account=None diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 735302a0c3..6c2fd67a9a 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -73,8 +73,10 @@ def create_asset_data(): 'doctype': 'Location', 'location_name': 'Test Location' }).insert() - + if not frappe.db.exists("Item", "Photocopier"): + meta = frappe.get_meta('Asset') + naming_series = meta.get_field("naming_series").options frappe.get_doc({ "doctype": "Item", "item_code": "Photocopier", @@ -83,7 +85,9 @@ def create_asset_data(): "company": "_Test Company", "is_fixed_asset": 1, "is_stock_item": 0, - "asset_category": "Equipment" + "asset_category": "Equipment", + "auto_create_assets": 1, + "asset_naming_series": naming_series }).insert() def create_maintenance_team(): diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index 7ef6461b5a..a71212ea47 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -2,27 +2,138 @@ // For license information, please see license.txt frappe.ui.form.on('Asset Movement', { - select_serial_no: function(frm) { - if (frm.doc.select_serial_no) { - let serial_no = frm.doc.serial_no - ? frm.doc.serial_no + '\n' + frm.doc.select_serial_no : frm.doc.select_serial_no; - frm.set_value("serial_no", serial_no); - frm.set_value("quantity", serial_no.split('\n').length); - } - }, - - serial_no: function(frm) { - const qty = frm.doc.serial_no ? frm.doc.serial_no.split('\n').length : 0; - frm.set_value("quantity", qty); - }, - - setup: function(frm) { - frm.set_query("select_serial_no", function() { + setup: (frm) => { + frm.set_query("to_employee", "assets", (doc) => { return { filters: { - "asset": frm.doc.asset + company: doc.company } }; + }) + frm.set_query("from_employee", "assets", (doc) => { + return { + filters: { + company: doc.company + } + }; + }) + frm.set_query("reference_name", (doc) => { + return { + filters: { + company: doc.company, + docstatus: 1 + } + }; + }) + frm.set_query("reference_doctype", () => { + return { + filters: { + name: ["in", ["Purchase Receipt", "Purchase Invoice"]] + } + }; + }) + }, + + onload: (frm) => { + frm.trigger('set_required_fields'); + }, + + purpose: (frm) => { + frm.trigger('set_required_fields'); + }, + + set_required_fields: (frm, cdt, cdn) => { + let fieldnames_to_be_altered; + if (frm.doc.purpose === 'Transfer') { + fieldnames_to_be_altered = { + target_location: { read_only: 0, reqd: 1 }, + source_location: { read_only: 1, reqd: 1 }, + from_employee: { read_only: 1, reqd: 0 }, + to_employee: { read_only: 1, reqd: 0 } + }; + } + else if (frm.doc.purpose === 'Receipt') { + fieldnames_to_be_altered = { + target_location: { read_only: 0, reqd: 1 }, + source_location: { read_only: 1, reqd: 0 }, + from_employee: { read_only: 0, reqd: 1 }, + to_employee: { read_only: 1, reqd: 0 } + }; + } + else if (frm.doc.purpose === 'Issue') { + fieldnames_to_be_altered = { + target_location: { read_only: 1, reqd: 0 }, + source_location: { read_only: 1, reqd: 1 }, + from_employee: { read_only: 1, reqd: 0 }, + to_employee: { read_only: 0, reqd: 1 } + }; + } + Object.keys(fieldnames_to_be_altered).forEach(fieldname => { + let property_to_be_altered = fieldnames_to_be_altered[fieldname]; + Object.keys(property_to_be_altered).forEach(property => { + let value = property_to_be_altered[property]; + frm.set_df_property(fieldname, property, value, cdn, 'assets'); + }); }); + frm.refresh_field('assets'); + }, + + reference_name: function(frm) { + if (frm.doc.reference_name && frm.doc.reference_doctype) { + const reference_doctype = frm.doc.reference_doctype === 'Purchase Invoice' ? 'purchase_invoice' : 'purchase_receipt'; + // On selection of reference name, + // sets query to display assets linked to that reference doc + frm.set_query('asset', 'assets', function() { + return { + filters: { + [reference_doctype] : frm.doc.reference_name + } + }; + }); + + // fetches linked asset & adds to the assets table + frappe.db.get_list('Asset', { + fields: ['name', 'location', 'custodian'], + filters: { + [reference_doctype] : frm.doc.reference_name + } + }).then((docs) => { + if (docs.length == 0) { + frappe.msgprint(frappe._(`Please select ${frm.doc.reference_doctype} which has assets.`)); + frm.doc.reference_name = ''; + frm.refresh_field('reference_name'); + return; + } + frm.doc.assets = []; + docs.forEach(doc => { + frm.add_child('assets', { + asset: doc.name, + source_location: doc.location, + from_employee: doc.custodian + }); + frm.refresh_field('assets'); + }) + }).catch((err) => { + console.log(err); // eslint-disable-line + }); + } else { + // if reference is deleted then remove query + frm.set_query('asset', 'assets', () => ({ filters: {} })); + } } }); + +frappe.ui.form.on('Asset Movement Item', { + asset: function(frm, cdt, cdn) { + // on manual entry of an asset auto sets their source location / employee + const asset_name = locals[cdt][cdn].asset; + if (asset_name){ + frappe.db.get_doc('Asset', asset_name).then((asset_doc) => { + if(asset_doc.location) frappe.model.set_value(cdt, cdn, 'source_location', asset_doc.location); + if(asset_doc.custodian) frappe.model.set_value(cdt, cdn, 'from_employee', asset_doc.custodian); + }).catch((err) => { + console.log(err); + }); + } + } +}); \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index 68076e1f74..19af81d65b 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -1,27 +1,20 @@ { "allow_import": 1, - "autoname": "naming_series:", + "autoname": "format:ACC-ASM-{YYYY}-{#####}", "creation": "2016-04-25 18:00:23.559973", "doctype": "DocType", + "engine": "InnoDB", "field_order": [ - "naming_series", "company", "purpose", - "asset", - "transaction_date", "column_break_4", - "quantity", - "select_serial_no", - "serial_no", - "section_break_7", - "source_location", - "target_location", - "column_break_10", - "from_employee", - "to_employee", + "transaction_date", "reference", "reference_doctype", + "column_break_9", "reference_name", + "section_break_10", + "assets", "amended_from" ], "fields": [ @@ -36,23 +29,12 @@ "reqd": 1 }, { - "default": "Transfer", "fieldname": "purpose", "fieldtype": "Select", "label": "Purpose", "options": "\nIssue\nReceipt\nTransfer", "reqd": 1 }, - { - "fieldname": "asset", - "fieldtype": "Link", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Asset", - "options": "Asset", - "reqd": 1 - }, { "fieldname": "transaction_date", "fieldtype": "Datetime", @@ -64,56 +46,6 @@ "fieldname": "column_break_4", "fieldtype": "Column Break" }, - { - "fieldname": "quantity", - "fieldtype": "Float", - "label": "Quantity" - }, - { - "fieldname": "select_serial_no", - "fieldtype": "Link", - "label": "Select Serial No", - "options": "Serial No" - }, - { - "fieldname": "serial_no", - "fieldtype": "Small Text", - "label": "Serial No" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - }, - { - "fieldname": "source_location", - "fieldtype": "Link", - "label": "Source Location", - "options": "Location" - }, - { - "fieldname": "target_location", - "fieldtype": "Link", - "label": "Target Location", - "options": "Location" - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "fieldname": "from_employee", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "From Employee", - "options": "Employee" - }, - { - "fieldname": "to_employee", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "To Employee", - "options": "Employee" - }, { "fieldname": "reference", "fieldtype": "Section Break", @@ -125,7 +57,7 @@ "label": "Reference DocType", "no_copy": 1, "options": "DocType", - "read_only": 1 + "reqd": 1 }, { "fieldname": "reference_name", @@ -133,7 +65,7 @@ "label": "Reference Name", "no_copy": 1, "options": "reference_doctype", - "read_only": 1 + "reqd": 1 }, { "fieldname": "amended_from", @@ -145,16 +77,23 @@ "read_only": 1 }, { - "default": "ACC-ASM-.YYYY.-", - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "ACC-ASM-.YYYY.-", + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "assets", + "fieldtype": "Table", + "label": "Assets", + "options": "Asset Movement Item", "reqd": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" } ], "is_submittable": 1, - "modified": "2019-09-16 16:27:53.887634", + "modified": "2019-11-13 15:37:48.870147", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index a1d3308b4d..714845dfac 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -5,101 +5,142 @@ from __future__ import unicode_literals import frappe from frappe import _ -from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from frappe.model.document import Document class AssetMovement(Document): def validate(self): self.validate_asset() self.validate_location() + self.validate_employee() def validate_asset(self): - status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) - if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"): - frappe.throw(_("{0} asset cannot be transferred").format(status)) + for d in self.assets: + status, company = frappe.db.get_value("Asset", d.asset, ["status", "company"]) + if self.purpose == 'Transfer' and 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)) + if company != self.company: + frappe.throw(_("Asset {0} does not belong to company {1}").format(d.asset, self.company)) - if self.serial_no and len(get_serial_nos(self.serial_no)) != self.quantity: - frappe.throw(_("Number of serial nos and quantity must be the same")) - - if not(self.source_location or self.target_location or self.from_employee or self.to_employee): - frappe.throw(_("Either location or employee must be required")) - - if (not self.serial_no and - frappe.db.get_value('Serial No', {'asset': self.asset}, 'name')): - frappe.throw(_("Serial no is required for the asset {0}").format(self.asset)) + if not(d.source_location or d.target_location or d.from_employee or d.to_employee): + frappe.throw(_("Either location or employee must be required")) def validate_location(self): - if self.purpose in ['Transfer', 'Issue']: - if not self.serial_no and not (self.from_employee or self.to_employee): - self.source_location = frappe.db.get_value("Asset", self.asset, "location") + for d in self.assets: + if self.purpose in ['Transfer', 'Issue']: + if not d.source_location: + d.source_location = frappe.db.get_value("Asset", d.asset, "location") - if self.purpose == 'Issue' and not (self.source_location or self.from_employee): - frappe.throw(_("Source Location is required for the asset {0}").format(self.asset)) + if not d.source_location: + frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset)) - if self.serial_no and self.source_location: - s_nos = get_serial_nos(self.serial_no) - serial_nos = frappe.db.sql_list(""" select name from `tabSerial No` where location != '%s' - and name in (%s)""" %(self.source_location, ','.join(['%s'] * len(s_nos))), tuple(s_nos)) + if d.source_location: + current_location = frappe.db.get_value("Asset", d.asset, "location") - if serial_nos: - frappe.throw(_("Serial nos {0} does not belongs to the location {1}"). - format(','.join(serial_nos), self.source_location)) + if current_location != d.source_location: + frappe.throw(_("Asset {0} does not belongs to the location {1}"). + format(d.asset, d.source_location)) + + if self.purpose == 'Issue': + if d.target_location: + frappe.throw(_("Issuing cannot be done to a location. \ + Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose") + if not d.to_employee: + frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset)) + + if self.purpose == 'Transfer': + if d.to_employee: + frappe.throw(_("Transferring cannot be done to an Employee. \ + Please enter location where Asset {0} has to be transferred").format( + d.asset), title="Incorrect Movement Purpose") + if not d.target_location: + frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset)) + if d.source_location == d.target_location: + frappe.throw(_("Source and Target Location cannot be same")) + + if self.purpose == 'Receipt': + # only when asset is bought and first entry is made + if not d.source_location and not (d.target_location or d.to_employee): + frappe.throw(_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)) + elif d.source_location: + # when asset is received from an employee + if d.target_location and not d.from_employee: + frappe.throw(_("From employee is required while receiving Asset {0} to a target location").format(d.asset)) + if d.from_employee and not d.target_location: + frappe.throw(_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)) + if d.to_employee and d.target_location: + frappe.throw(_("Asset {0} cannot be received at a location and \ + given to employee in a single movement").format(d.asset)) - if self.source_location and self.source_location == self.target_location and self.purpose == 'Transfer': - frappe.throw(_("Source and Target Location cannot be same")) + def validate_employee(self): + for d in self.assets: + if d.from_employee: + current_custodian = frappe.db.get_value("Asset", d.asset, "custodian") - if self.purpose == 'Receipt' and not (self.target_location or self.to_employee): - frappe.throw(_("Target Location is required for the asset {0}").format(self.asset)) + if current_custodian != d.from_employee: + frappe.throw(_("Asset {0} does not belongs to the custodian {1}"). + format(d.asset, d.from_employee)) + + if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company: + frappe.throw(_("Employee {0} does not belongs to the company {1}"). + format(d.to_employee, self.company)) def on_submit(self): self.set_latest_location_in_asset() + + def before_cancel(self): + self.validate_last_movement() def on_cancel(self): self.set_latest_location_in_asset() + + def validate_last_movement(self): + for d in self.assets: + auto_gen_movement_entry = frappe.db.sql( + """ + SELECT asm.name + FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm + WHERE + asm.docstatus=1 and + asm_item.parent=asm.name and + asm_item.asset=%s and + asm.company=%s and + asm_item.source_location is NULL and + asm.purpose=%s + ORDER BY + asm.transaction_date asc + """, (d.asset, self.company, 'Receipt'), as_dict=1) + if auto_gen_movement_entry[0].get('name') == self.name: + frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \ + auto generated for Asset {1}').format(self.name, d.asset)) def set_latest_location_in_asset(self): - location, employee = '', '' + current_location, current_employee = '', '' cond = "1=1" - args = { - 'asset': self.asset, - 'company': self.company - } + for d in self.assets: + args = { + 'asset': d.asset, + 'company': self.company + } - if self.serial_no: - cond = "serial_no like %(txt)s" - args.update({ - 'txt': "%%%s%%" % self.serial_no - }) + # latest entry corresponds to current document's location, employee when transaction date > previous dates + # In case of cancellation it corresponds to previous latest document's location, employee + latest_movement_entry = frappe.db.sql( + """ + SELECT asm_item.target_location, asm_item.to_employee + FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm + WHERE + asm_item.parent=asm.name and + asm_item.asset=%(asset)s and + asm.company=%(company)s and + asm.docstatus=1 and {0} + ORDER BY + asm.transaction_date desc limit 1 + """.format(cond), args) + if latest_movement_entry: + current_location = latest_movement_entry[0][0] + current_employee = latest_movement_entry[0][1] - latest_movement_entry = frappe.db.sql("""select target_location, to_employee from `tabAsset Movement` - where asset=%(asset)s and docstatus=1 and company=%(company)s and {0} - order by transaction_date desc limit 1""".format(cond), args) - - if latest_movement_entry: - location = latest_movement_entry[0][0] - employee = latest_movement_entry[0][1] - elif self.purpose in ['Transfer', 'Receipt']: - movement_entry = frappe.db.sql("""select source_location, from_employee from `tabAsset Movement` - where asset=%(asset)s and docstatus=2 and company=%(company)s and {0} - order by transaction_date asc limit 1""".format(cond), args) - if movement_entry: - location = movement_entry[0][0] - employee = movement_entry[0][1] - - if not self.serial_no: - frappe.db.set_value("Asset", self.asset, "location", location) - - if not employee and self.purpose in ['Receipt', 'Transfer']: - employee = self.to_employee - - if self.serial_no: - for d in get_serial_nos(self.serial_no): - if (location or (self.purpose == 'Issue' and self.source_location)): - frappe.db.set_value('Serial No', d, 'location', location) - - if employee or self.docstatus==2 or self.purpose == 'Issue': - frappe.db.set_value('Serial No', d, 'employee', employee) + frappe.db.set_value('Asset', d.asset, 'location', current_location) + frappe.db.set_value('Asset', d.asset, 'custodian', current_employee) diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 4d85337445..c3755a3fb9 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import unittest +import erpnext from erpnext.stock.doctype.item.test_item import make_item from frappe.utils import now, nowdate, get_last_day, add_days from erpnext.assets.doctype.asset.test_asset import create_asset_data @@ -16,7 +17,6 @@ class TestAssetMovement(unittest.TestCase): def setUp(self): create_asset_data() make_location() - make_serialized_item() def test_movement(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -38,68 +38,72 @@ class TestAssetMovement(unittest.TestCase): if asset.docstatus == 0: asset.submit() + + # check asset movement is created if not frappe.db.exists("Location", "Test Location 2"): frappe.get_doc({ 'doctype': 'Location', 'location_name': 'Test Location 2' }).insert() - movement1 = create_asset_movement(asset= asset.name, purpose = 'Transfer', - company=asset.company, source_location="Test Location", target_location="Test Location 2") + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(asset= asset.name, purpose = 'Transfer', - company=asset.company, source_location = "Test Location 2", target_location="Test Location") + movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") movement1.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") - movement2.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") - - def test_movement_for_serialized_asset(self): - asset_item = "Test Serialized Asset Item" - pr = make_purchase_receipt(item_code=asset_item, rate = 1000, qty=3, location = "Mumbai") - asset_name = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') - + employee = make_employee("testassetmovemp@example.com", company="_Test Company") + movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) + + # after issuing asset should belong to an employee not at a location + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) + self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) + + def test_last_movement_cancellation(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) - month_end_date = get_last_day(nowdate()) - asset.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { - "expected_value_after_useful_life": 200, + "expected_value_after_useful_life": 10000, + "next_depreciation_date": "2020-12-31", "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, "frequency_of_depreciation": 10, - "depreciation_start_date": month_end_date + "depreciation_start_date": "2020-06-06" }) - asset.submit() - serial_nos = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'serial_no') + if asset.docstatus == 0: + asset.submit() + + if not frappe.db.exists("Location", "Test Location 2"): + frappe.get_doc({ + 'doctype': 'Location', + 'location_name': 'Test Location 2' + }).insert() + + movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name }) + self.assertRaises(frappe.ValidationError, movement.cancel) - mov1 = create_asset_movement(asset=asset_name, purpose = 'Transfer', - company=asset.company, source_location = "Mumbai", target_location="Pune", serial_no=serial_nos) - self.assertEqual(mov1.target_location, "Pune") + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - serial_no = frappe.db.get_value('Serial No', {'asset': asset_name}, 'name') - - employee = make_employee("testassetemp@example.com") - create_asset_movement(asset=asset_name, purpose = 'Transfer', - company=asset.company, serial_no=serial_no, to_employee=employee) - - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), employee) - - create_asset_movement(asset=asset_name, purpose = 'Transfer', company=asset.company, - serial_no=serial_no, from_employee=employee, to_employee="_T-Employee-00001") - - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Pune") - - mov4 = create_asset_movement(asset=asset_name, purpose = 'Transfer', - company=asset.company, source_location = "Pune", target_location="Nagpur", serial_no=serial_nos) - self.assertEqual(mov4.target_location, "Nagpur") - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Nagpur") - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), "_T-Employee-00001") + movement1.cancel() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") def create_asset_movement(**args): args = frappe._dict(args) @@ -109,22 +113,14 @@ def create_asset_movement(**args): movement = frappe.new_doc("Asset Movement") movement.update({ - "asset": args.asset, + "assets": args.assets, "transaction_date": args.transaction_date, - "target_location": args.target_location, "company": args.company, 'purpose': args.purpose or 'Receipt', - 'serial_no': args.serial_no, - 'quantity': len(get_serial_nos(args.serial_no)) if args.serial_no else 1, - 'from_employee': "_T-Employee-00001" or args.from_employee, - 'to_employee': args.to_employee + 'reference_doctype': args.reference_doctype, + 'reference_name': args.reference_name }) - if args.source_location: - movement.update({ - 'source_location': args.source_location - }) - movement.insert() movement.submit() @@ -137,33 +133,3 @@ def make_location(): 'doctype': 'Location', 'location_name': location }).insert(ignore_permissions = True) - -def make_serialized_item(): - asset_item = "Test Serialized Asset Item" - - if not frappe.db.exists('Item', asset_item): - asset_category = frappe.get_all('Asset Category') - - if asset_category: - asset_category = asset_category[0].name - - if not asset_category: - doc = frappe.get_doc({ - 'doctype': 'Asset Category', - 'asset_category_name': 'Test Asset Category', - 'depreciation_method': 'Straight Line', - 'total_number_of_depreciations': 12, - 'frequency_of_depreciation': 1, - 'accounts': [{ - 'company_name': '_Test Company', - 'fixed_asset_account': '_Test Fixed Asset - _TC', - 'accumulated_depreciation_account': 'Depreciation - _TC', - 'depreciation_expense_account': 'Depreciation - _TC' - }] - }).insert() - - asset_category = doc.name - - make_item(asset_item, {'is_stock_item':0, - 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, - 'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) diff --git a/erpnext/assets/doctype/asset_movement_item/__init__.py b/erpnext/assets/doctype/asset_movement_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json new file mode 100644 index 0000000000..994c3c0989 --- /dev/null +++ b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json @@ -0,0 +1,86 @@ +{ + "creation": "2019-10-07 18:49:00.737806", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "asset", + "source_location", + "from_employee", + "column_break_2", + "asset_name", + "target_location", + "to_employee" + ], + "fields": [ + { + "fieldname": "asset", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset", + "options": "Asset", + "reqd": 1 + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "source_location", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Source Location", + "options": "Location" + }, + { + "fieldname": "target_location", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Target Location", + "options": "Location" + }, + { + "fieldname": "from_employee", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "From Employee", + "options": "Employee" + }, + { + "fieldname": "to_employee", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "To Employee", + "options": "Employee" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "hidden": 1, + "label": "Company", + "options": "Company", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2019-10-09 15:59:08.265141", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Movement Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement_item/asset_movement_item.py b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.py new file mode 100644 index 0000000000..4c6aaab58a --- /dev/null +++ b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, 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 AssetMovementItem(Document): + pass diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 66ad97ac09..c409c1f46e 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -43,6 +43,7 @@ "base_amount", "pricing_rules", "is_free_item", + "is_fixed_asset", "section_break_29", "net_rate", "net_amount", @@ -699,11 +700,19 @@ "fieldtype": "Data", "label": "Manufacturer Part Number", "read_only": 1 + }, + { + "default": "0", + "fetch_from": "item_code.is_fixed_asset", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "label": "Is Fixed Asset", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-09-17 22:32:34.703923", + "modified": "2019-11-07 17:19:12.090355", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9415228467..a912ef00d1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -718,48 +718,6 @@ class AccountsController(TransactionBase): # at quotation / sales order level and we shouldn't stop someone # from creating a sales invoice if sales order is already created - def validate_fixed_asset(self): - for d in self.get("items"): - if d.is_fixed_asset: - # if d.qty > 1: - # frappe.throw(_("Row #{0}: Qty must be 1, as item is a fixed asset. Please use separate row for multiple qty.").format(d.idx)) - - if d.meta.get_field("asset") and d.asset: - asset = frappe.get_doc("Asset", d.asset) - - if asset.company != self.company: - frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}") - .format(d.idx, d.asset, self.company)) - - elif asset.item_code != d.item_code: - frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}") - .format(d.idx, d.asset, d.item_code)) - - # elif asset.docstatus != 1: - # frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset)) - - elif self.doctype == "Purchase Invoice": - # if asset.status != "Submitted": - # frappe.throw(_("Row #{0}: Asset {1} is already {2}") - # .format(d.idx, d.asset, asset.status)) - if getdate(asset.purchase_date) != getdate(self.posting_date): - frappe.throw( - _("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx, - asset.purchase_date, - d.asset)) - elif asset.is_existing_asset: - frappe.throw( - _("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format( - d.idx, d.asset)) - - elif self.docstatus == "Sales Invoice" and self.docstatus == 1: - if self.update_stock: - frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) - - elif asset.status in ("Scrapped", "Cancelled", "Sold"): - frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}") - .format(d.idx, d.asset, asset.status)) - def delink_advance_entries(self, linked_doc_name): total_allocated_amount = 0 for adv in self.advances: diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0dde898005..d0befcbcf3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -101,7 +101,7 @@ class BuyingController(StockController): msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) def get_asset_items(self): - if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']: + if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']: return [] return [d.item_code for d in self.items if d.is_fixed_asset] @@ -150,25 +150,26 @@ class BuyingController(StockController): TODO: rename item_tax_amount to valuation_tax_amount """ - stock_items = self.get_stock_items() + self.get_asset_items() + stock_and_asset_items = self.get_stock_items() + self.get_asset_items() - stock_items_qty, stock_items_amount = 0, 0 - last_stock_item_idx = 1 + stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0 + last_item_idx = 1 for d in self.get(parentfield): - if d.item_code and d.item_code in stock_items: - stock_items_qty += flt(d.qty) - stock_items_amount += flt(d.base_net_amount) - last_stock_item_idx = d.idx + if d.item_code and d.item_code in stock_and_asset_items: + stock_and_asset_items_qty += flt(d.qty) + stock_and_asset_items_amount += flt(d.base_net_amount) + last_item_idx = d.idx total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes") if d.category in ["Valuation", "Valuation and Total"]]) valuation_amount_adjustment = total_valuation_amount for i, item in enumerate(self.get(parentfield)): - if item.item_code and item.qty and item.item_code in stock_items: - item_proportion = flt(item.base_net_amount) / stock_items_amount if stock_items_amount \ - else flt(item.qty) / stock_items_qty - if i == (last_stock_item_idx - 1): + if item.item_code and item.qty and item.item_code in stock_and_asset_items: + item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \ + else flt(item.qty) / stock_and_asset_items_qty + + if i == (last_item_idx - 1): item.item_tax_amount = flt(valuation_amount_adjustment, self.precision("item_tax_amount", item)) else: @@ -572,43 +573,28 @@ class BuyingController(StockController): asset_items = self.get_asset_items() if asset_items: - self.make_serial_nos_for_asset(asset_items) + self.auto_make_assets(asset_items) - def make_serial_nos_for_asset(self, asset_items): + def auto_make_assets(self, asset_items): items_data = get_asset_item_details(asset_items) for d in self.items: if d.is_fixed_asset: item_data = items_data.get(d.item_code) - if not d.asset: - asset = self.make_asset(d) - d.db_set('asset', asset) - if item_data.get('has_serial_no'): - # If item has serial no - if item_data.get('serial_no_series') and not d.serial_no: - serial_nos = get_auto_serial_nos(item_data.get('serial_no_series'), d.qty) - elif d.serial_no: - serial_nos = d.serial_no - elif not d.serial_no: - frappe.throw(_("Serial no is mandatory for the item {0}").format(d.item_code)) - - auto_make_serial_nos({ - 'serial_no': serial_nos, - 'item_code': d.item_code, - 'via_stock_ledger': False, - 'company': self.company, - 'supplier': self.supplier, - 'actual_qty': d.qty, - 'purchase_document_type': self.doctype, - 'purchase_document_no': self.name, - 'asset': d.asset, - 'location': d.asset_location - }) - d.db_set('serial_no', serial_nos) - - if d.asset: - self.make_asset_movement(d) + if item_data.get('auto_create_assets'): + # If asset has to be auto created + # Check for asset naming series + if item_data.get('asset_naming_series'): + for qty in range(cint(d.qty)): + self.make_asset(d) + is_plural = 's' if cint(d.qty) != 1 else '' + frappe.msgprint(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural)) + else: + frappe.throw(_("Asset Naming Series is mandatory for the auto creation for item {0}").format(d.item_code)) + else: + frappe.msgprint(_("Assets not created. You will have to create asset manually.")) + def make_asset(self, row): if not row.asset_location: @@ -617,7 +603,7 @@ class BuyingController(StockController): item_data = frappe.db.get_value('Item', row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) - purchase_amount = flt(row.base_net_amount + row.item_tax_amount) + purchase_amount = flt(row.base_rate + row.item_tax_amount) asset = frappe.get_doc({ 'doctype': 'Asset', 'item_code': row.item_code, @@ -640,57 +626,42 @@ class BuyingController(StockController): asset.set_missing_values() asset.insert() - asset_link = frappe.utils.get_link_to_form('Asset', asset.name) - frappe.msgprint(_("Asset {0} created").format(asset_link)) - return asset.name - - def make_asset_movement(self, row): - asset_movement = frappe.get_doc({ - 'doctype': 'Asset Movement', - 'asset': row.asset, - 'target_location': row.asset_location, - 'purpose': 'Receipt', - 'serial_no': row.serial_no, - 'quantity': len(get_serial_nos(row.serial_no)), - 'company': self.company, - 'transaction_date': self.posting_date, - 'reference_doctype': self.doctype, - 'reference_name': self.name - }).insert() - - return asset_movement.name - def update_fixed_asset(self, field, delete_asset = False): for d in self.get("items"): - if d.is_fixed_asset and d.asset: - asset = frappe.get_doc("Asset", d.asset) + if d.is_fixed_asset: + is_auto_create_enabled = frappe.db.get_value('Item', d.item_code, 'auto_create_assets') + assets = frappe.db.get_all('Asset', filters={ field : self.name, 'item_code' : d.item_code }) - if delete_asset and asset.docstatus == 0: - frappe.delete_doc("Asset", asset.name) - d.db_set('asset', None) - continue + for asset in assets: + asset = frappe.get_doc('Asset', asset.name) + if delete_asset and is_auto_create_enabled: + # need to delete movements to delete assets otherwise throws link exists error + movements = frappe.db.get_all('Asset Movement', filters={ 'reference_name': self.name }) + for movement in movements: + frappe.delete_doc('Asset Movement', movement.name, force=1) + frappe.delete_doc("Asset", asset.name, force=1) + continue - if self.docstatus in [0, 1] and not asset.get(field): - asset.set(field, self.name) - asset.purchase_date = self.posting_date - asset.supplier = self.supplier - elif self.docstatus == 2: - asset.set(field, None) - asset.supplier = None + if self.docstatus in [0, 1] and not asset.get(field): + asset.set(field, self.name) + asset.purchase_date = self.posting_date + asset.supplier = self.supplier + elif self.docstatus == 2: + asset.set(field, None) + asset.supplier = None - asset.flags.ignore_validate_update_after_submit = True - asset.flags.ignore_mandatory = True - if asset.docstatus == 0: - asset.flags.ignore_validate = True + asset.flags.ignore_validate_update_after_submit = True + asset.flags.ignore_mandatory = True + if asset.docstatus == 0: + asset.flags.ignore_validate = True - asset.save() + asset.save() def delete_linked_asset(self): if self.doctype == 'Purchase Invoice' and not self.get('update_stock'): return frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name) - frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) def validate_schedule_date(self): if not self.get("items"): @@ -764,7 +735,7 @@ def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchas def get_asset_item_details(asset_items): asset_items_data = {} - for d in frappe.get_all('Item', fields = ["name", "has_serial_no", "serial_no_series"], + for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"], filters = {'name': ('in', asset_items)}): asset_items_data.setdefault(d.name, d) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index a9e50bab5a..3830ca0361 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -476,3 +476,29 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters) as_list=1 ) return item_manufacturers + +@frappe.whitelist() +def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): + query = """ + select pr.name + from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem + where pr.docstatus = 1 and pritem.parent = pr.name + and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt))) + + if filters and filters.get('item_code'): + query += " and pritem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code'))) + + return frappe.db.sql(query, filters) + +@frappe.whitelist() +def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): + query = """ + select pi.name + from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem + where pi.docstatus = 1 and piitem.parent = pi.name + and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt))) + + if filters and filters.get('item_code'): + query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code'))) + + return frappe.db.sql(query, filters) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 5a63beb283..d3410de2eb 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -45,7 +45,7 @@ class TestEmployee(unittest.TestCase): employee1_doc.status = 'Left' self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) -def make_employee(user): +def make_employee(user, company=None): if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -55,12 +55,12 @@ def make_employee(user): "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() - if not frappe.db.get_value("Employee", {"user_id": user}): + if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }): employee = frappe.get_doc({ "doctype": "Employee", "naming_series": "EMP-", "first_name": user, - "company": erpnext.get_default_company(), + "company": company or erpnext.get_default_company(), "user_id": user, "date_of_birth": "1990-05-08", "date_of_joining": "2013-01-01", diff --git a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py index 1c8bd68932..ee709ac2d4 100644 --- a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py +++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py @@ -17,10 +17,6 @@ def execute(): frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh set ast.location = wh.warehouse_name where ast.warehouse = wh.name""") - frappe.db.sql(""" update `tabAsset Movement` ast_mv - set ast_mv.source_location = (select warehouse_name from `tabWarehouse` where name = ast_mv.source_warehouse), - ast_mv.target_location = (select warehouse_name from `tabWarehouse` where name = ast_mv.target_warehouse)""") - for d in frappe.get_all('Asset'): doc = frappe.get_doc('Asset', d.name) if doc.calculate_depreciation: diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index bfc5e6d438..2f4abbcea6 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -25,7 +25,7 @@ frappe.ui.form.on("Item", { }, refresh: function(frm) { - if(frm.doc.is_stock_item) { + if (frm.doc.is_stock_item) { frm.add_custom_button(__("Balance"), function() { frappe.route_options = { "item_code": frm.doc.name @@ -46,10 +46,15 @@ frappe.ui.form.on("Item", { }, __("View")); } - if(!frm.doc.is_fixed_asset) { + if (!frm.doc.is_fixed_asset) { erpnext.item.make_dashboard(frm); } + if (frm.doc.is_fixed_asset) { + frm.trigger('is_fixed_asset'); + frm.trigger('auto_create_assets'); + } + // clear intro frm.set_intro(); @@ -132,6 +137,11 @@ frappe.ui.form.on("Item", { }, is_fixed_asset: function(frm) { + // set serial no to false & toggles its visibility + frm.set_value('has_serial_no', 0); + frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); + frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); + frm.call({ method: "set_asset_naming_series", doc: frm.doc, @@ -139,7 +149,7 @@ frappe.ui.form.on("Item", { frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); frm.trigger("set_asset_naming_series"); } - }) + }); }, set_asset_naming_series: function(frm) { @@ -148,6 +158,11 @@ frappe.ui.form.on("Item", { } }, + auto_create_assets: function(frm) { + frm.toggle_reqd(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets); + frm.toggle_display(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets); + }, + page_name: frappe.utils.warn_page_name_change, item_code: function(frm) { diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 46efd4ee26..a2aab3f69e 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1,1105 +1,1114 @@ { - "allow_guest_to_view": 1, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:item_code", - "creation": "2013-05-03 10:45:46", - "description": "A Product or a Service that is bought, sold or kept in stock.", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "name_and_description_section", - "naming_series", - "item_code", - "variant_of", - "item_name", - "item_group", - "is_item_from_hub", - "stock_uom", - "column_break0", - "disabled", - "allow_alternative_item", - "is_stock_item", - "include_item_in_manufacturing", - "opening_stock", - "valuation_rate", - "standard_rate", - "is_fixed_asset", - "asset_category", - "asset_naming_series", - "over_delivery_receipt_allowance", - "over_billing_allowance", - "image", - "section_break_11", - "brand", - "description", - "sb_barcodes", - "barcodes", - "inventory_section", - "shelf_life_in_days", - "end_of_life", - "default_material_request_type", - "valuation_method", - "column_break1", - "warranty_period", - "weight_per_unit", - "weight_uom", - "reorder_section", - "reorder_levels", - "unit_of_measure_conversion", - "uoms", - "serial_nos_and_batches", - "has_batch_no", - "create_new_batch", - "batch_number_series", - "has_expiry_date", - "retain_sample", - "sample_quantity", - "column_break_37", - "has_serial_no", - "serial_no_series", - "variants_section", - "has_variants", - "variant_based_on", - "attributes", - "defaults", - "item_defaults", - "purchase_details", - "is_purchase_item", - "purchase_uom", - "min_order_qty", - "safety_stock", - "purchase_details_cb", - "lead_time_days", - "last_purchase_rate", - "is_customer_provided_item", - "customer", - "supplier_details", - "delivered_by_supplier", - "column_break2", - "supplier_items", - "foreign_trade_details", - "country_of_origin", - "column_break_59", - "customs_tariff_number", - "sales_details", - "sales_uom", - "is_sales_item", - "column_break3", - "max_discount", - "deferred_revenue", - "deferred_revenue_account", - "enable_deferred_revenue", - "column_break_85", - "no_of_months", - "deferred_expense_section", - "deferred_expense_account", - "enable_deferred_expense", - "column_break_88", - "no_of_months_exp", - "customer_details", - "customer_items", - "item_tax_section_break", - "taxes", - "inspection_criteria", - "inspection_required_before_purchase", - "inspection_required_before_delivery", - "quality_inspection_template", - "manufacturing", - "default_bom", - "is_sub_contracted_item", - "column_break_74", - "customer_code", - "website_section", - "show_in_website", - "show_variant_in_website", - "route", - "weightage", - "slideshow", - "website_image", - "thumbnail", - "cb72", - "website_warehouse", - "website_item_groups", - "set_meta_tags", - "sb72", - "copy_from_item_group", - "website_specifications", - "web_long_description", - "website_content", - "total_projected_qty", - "hub_publishing_sb", - "publish_in_hub", - "hub_category_to_publish", - "hub_warehouse", - "synced_with_hub" - ], - "fields": [ - { - "fieldname": "name_and_description_section", - "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "options": "fa fa-flag" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "STO-ITEM-.YYYY.-", - "set_only_once": 1 - }, - { - "bold": 1, - "fieldname": "item_code", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Item Code", - "oldfieldname": "item_code", - "oldfieldtype": "Data", - "unique": 1, - "reqd": 1 - }, - { - "depends_on": "variant_of", - "description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified", - "fieldname": "variant_of", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Variant Of", - "options": "Item", - "read_only": 1, - "search_index": 1, - "set_only_once": 1 - }, - { - "bold": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Item Name", - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "search_index": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "in_preview": 1, - "in_standard_filter": 1, - "label": "Item Group", - "oldfieldname": "item_group", - "oldfieldtype": "Link", - "options": "Item Group", - "reqd": 1, - "search_index": 1 - }, - { - "default": "0", - "fieldname": "is_item_from_hub", - "fieldtype": "Check", - "label": "Is Item from Hub", - "read_only": 1 - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Unit of Measure", - "oldfieldname": "stock_uom", - "oldfieldtype": "Link", - "options": "UOM", - "reqd": 1 - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled" - }, - { - "default": "0", - "fieldname": "allow_alternative_item", - "fieldtype": "Check", - "label": "Allow Alternative Item" - }, - { - "bold": 1, - "default": "1", - "fieldname": "is_stock_item", - "fieldtype": "Check", - "label": "Maintain Stock", - "oldfieldname": "is_stock_item", - "oldfieldtype": "Select" - }, - { - "default": "1", - "fieldname": "include_item_in_manufacturing", - "fieldtype": "Check", - "label": "Include Item In Manufacturing" - }, - { - "bold": 1, - "depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)", - "fieldname": "opening_stock", - "fieldtype": "Float", - "label": "Opening Stock" - }, - { - "depends_on": "is_stock_item", - "fieldname": "valuation_rate", - "fieldtype": "Currency", - "label": "Valuation Rate" - }, - { - "bold": 1, - "depends_on": "eval:doc.__islocal", - "fieldname": "standard_rate", - "fieldtype": "Currency", - "label": "Standard Selling Rate" - }, - { - "default": "0", - "fieldname": "is_fixed_asset", - "fieldtype": "Check", - "label": "Is Fixed Asset", - "set_only_once": 1 - }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset_category", - "fieldtype": "Link", - "label": "Asset Category", - "options": "Asset Category" - }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset_naming_series", - "fieldtype": "Select", - "label": "Asset Naming Series" - }, - { - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "in_preview": 1, - "label": "Image", - "options": "image", - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_11", - "fieldtype": "Section Break", - "label": "Description" - }, - { - "fieldname": "brand", - "fieldtype": "Link", - "label": "Brand", - "oldfieldname": "brand", - "oldfieldtype": "Link", - "options": "Brand", - "print_hide": 1 - }, - { - "fieldname": "description", - "fieldtype": "Text Editor", - "in_preview": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text" - }, - { - "fieldname": "sb_barcodes", - "fieldtype": "Section Break", - "label": "Barcodes" - }, - { - "fieldname": "barcodes", - "fieldtype": "Table", - "label": "Barcodes", - "options": "Item Barcode" - }, - { - "collapsible": 1, - "collapsible_depends_on": "is_stock_item", - "depends_on": "is_stock_item", - "fieldname": "inventory_section", - "fieldtype": "Section Break", - "label": "Inventory", - "oldfieldtype": "Section Break", - "options": "fa fa-truck" - }, - { - "fieldname": "shelf_life_in_days", - "fieldtype": "Int", - "label": "Shelf Life In Days" - }, - { - "default": "2099-12-31", - "depends_on": "is_stock_item", - "fieldname": "end_of_life", - "fieldtype": "Date", - "label": "End of Life", - "oldfieldname": "end_of_life", - "oldfieldtype": "Date" - }, - { - "default": "Purchase", - "fieldname": "default_material_request_type", - "fieldtype": "Select", - "label": "Default Material Request Type", - "options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" - }, - { - "depends_on": "is_stock_item", - "fieldname": "valuation_method", - "fieldtype": "Select", - "label": "Valuation Method", - "options": "\nFIFO\nMoving Average", - "set_only_once": 1 - }, - { - "depends_on": "is_stock_item", - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "depends_on": "eval:doc.is_stock_item", - "fieldname": "warranty_period", - "fieldtype": "Data", - "label": "Warranty Period (in days)", - "oldfieldname": "warranty_period", - "oldfieldtype": "Data" - }, - { - "depends_on": "is_stock_item", - "fieldname": "weight_per_unit", - "fieldtype": "Float", - "label": "Weight Per Unit" - }, - { - "depends_on": "eval:doc.is_stock_item", - "fieldname": "weight_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Weight UOM", - "options": "UOM" - }, - { - "collapsible": 1, - "depends_on": "is_stock_item", - "fieldname": "reorder_section", - "fieldtype": "Section Break", - "label": "Auto re-order", - "options": "fa fa-rss" - }, - { - "description": "Will also apply for variants unless overrridden", - "fieldname": "reorder_levels", - "fieldtype": "Table", - "label": "Reorder level based on Warehouse", - "options": "Item Reorder" - }, - { - "collapsible": 1, - "fieldname": "unit_of_measure_conversion", - "fieldtype": "Section Break", - "label": "Units of Measure" - }, - { - "description": "Will also apply for variants", - "fieldname": "uoms", - "fieldtype": "Table", - "label": "UOMs", - "oldfieldname": "uom_conversion_details", - "oldfieldtype": "Table", - "options": "UOM Conversion Detail" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset", - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", - "fieldname": "serial_nos_and_batches", - "fieldtype": "Section Break", - "label": "Serial Nos and Batches" - }, - { - "default": "0", - "depends_on": "eval:doc.is_stock_item", - "fieldname": "has_batch_no", - "fieldtype": "Check", - "label": "Has Batch No", - "no_copy": 1, - "oldfieldname": "has_batch_no", - "oldfieldtype": "Select" - }, - { - "default": "0", - "depends_on": "has_batch_no", - "fieldname": "create_new_batch", - "fieldtype": "Check", - "label": "Automatically Create New Batch" - }, - { - "depends_on": "eval:doc.has_batch_no==1 && doc.create_new_batch==1", - "description": "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.", - "fieldname": "batch_number_series", - "fieldtype": "Data", - "label": "Batch Number Series", - "translatable": 1 - }, - { - "default": "0", - "depends_on": "has_batch_no", - "fieldname": "has_expiry_date", - "fieldtype": "Check", - "label": "Has Expiry Date" - }, - { - "default": "0", - "fieldname": "retain_sample", - "fieldtype": "Check", - "label": "Retain Sample" - }, - { - "depends_on": "eval: (doc.retain_sample && doc.has_batch_no)", - "description": "Maximum sample quantity that can be retained", - "fieldname": "sample_quantity", - "fieldtype": "Int", - "label": "Max Sample Quantity" - }, - { - "fieldname": "column_break_37", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", - "fieldname": "has_serial_no", - "fieldtype": "Check", - "label": "Has Serial No", - "no_copy": 1, - "oldfieldname": "has_serial_no", - "oldfieldtype": "Select" - }, - { - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", - "description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.", - "fieldname": "serial_no_series", - "fieldtype": "Data", - "label": "Serial Number Series" - }, - { - "collapsible": 1, - "collapsible_depends_on": "attributes", - "fieldname": "variants_section", - "fieldtype": "Section Break", - "label": "Variants" - }, - { - "default": "0", - "depends_on": "eval:!doc.variant_of", - "description": "If this item has variants, then it cannot be selected in sales orders etc.", - "fieldname": "has_variants", - "fieldtype": "Check", - "in_standard_filter": 1, - "label": "Has Variants", - "no_copy": 1 - }, - { - "default": "Item Attribute", - "depends_on": "has_variants", - "fieldname": "variant_based_on", - "fieldtype": "Select", - "label": "Variant Based On", - "options": "Item Attribute\nManufacturer" - }, - { - "depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", - "fieldname": "attributes", - "fieldtype": "Table", - "hidden": 1, - "label": "Attributes", - "no_copy": 1, - "options": "Item Variant Attribute" - }, - { - "fieldname": "defaults", - "fieldtype": "Section Break", - "label": "Sales, Purchase, Accounting Defaults" - }, - { - "fieldname": "item_defaults", - "fieldtype": "Table", - "label": "Item Defaults", - "options": "Item Default" - }, - { - "collapsible": 1, - "fieldname": "purchase_details", - "fieldtype": "Section Break", - "label": "Purchase, Replenishment Details", - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" - }, - { - "default": "1", - "fieldname": "is_purchase_item", - "fieldtype": "Check", - "label": "Is Purchase Item" - }, - { - "fieldname": "purchase_uom", - "fieldtype": "Link", - "label": "Default Purchase Unit of Measure", - "options": "UOM" - }, - { - "default": "0.00", - "depends_on": "is_stock_item", - "fieldname": "min_order_qty", - "fieldtype": "Float", - "label": "Minimum Order Qty", - "oldfieldname": "min_order_qty", - "oldfieldtype": "Currency" - }, - { - "fieldname": "safety_stock", - "fieldtype": "Float", - "label": "Safety Stock" - }, - { - "fieldname": "purchase_details_cb", - "fieldtype": "Column Break" - }, - { - "description": "Average time taken by the supplier to deliver", - "fieldname": "lead_time_days", - "fieldtype": "Int", - "label": "Lead Time in days", - "oldfieldname": "lead_time_days", - "oldfieldtype": "Int" - }, - { - "fieldname": "last_purchase_rate", - "fieldtype": "Float", - "label": "Last Purchase Rate", - "no_copy": 1, - "oldfieldname": "last_purchase_rate", - "oldfieldtype": "Currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "is_customer_provided_item", - "fieldtype": "Check", - "label": "Is Customer Provided Item" - }, - { - "depends_on": "eval:doc.is_customer_provided_item==1", - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "options": "Customer" - }, - { - "collapsible": 1, - "fieldname": "supplier_details", - "fieldtype": "Section Break", - "label": "Supplier Details" - }, - { - "default": "0", - "fieldname": "delivered_by_supplier", - "fieldtype": "Check", - "label": "Delivered by Supplier (Drop Ship)", - "print_hide": 1 - }, - { - "fieldname": "column_break2", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "supplier_items", - "fieldtype": "Table", - "label": "Supplier Items", - "options": "Item Supplier" - }, - { - "collapsible": 1, - "fieldname": "foreign_trade_details", - "fieldtype": "Section Break", - "label": "Foreign Trade Details" - }, - { - "fieldname": "country_of_origin", - "fieldtype": "Link", - "label": "Country of Origin", - "options": "Country" - }, - { - "fieldname": "column_break_59", - "fieldtype": "Column Break" - }, - { - "fieldname": "customs_tariff_number", - "fieldtype": "Link", - "label": "Customs Tariff Number", - "options": "Customs Tariff Number" - }, - { - "collapsible": 1, - "fieldname": "sales_details", - "fieldtype": "Section Break", - "label": "Sales Details", - "oldfieldtype": "Section Break", - "options": "fa fa-tag" - }, - { - "fieldname": "sales_uom", - "fieldtype": "Link", - "label": "Default Sales Unit of Measure", - "options": "UOM" - }, - { - "default": "1", - "fieldname": "is_sales_item", - "fieldtype": "Check", - "label": "Is Sales Item" - }, - { - "fieldname": "column_break3", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "max_discount", - "fieldtype": "Float", - "label": "Max Discount (%)", - "oldfieldname": "max_discount", - "oldfieldtype": "Currency" - }, - { - "collapsible": 1, - "fieldname": "deferred_revenue", - "fieldtype": "Section Break", - "label": "Deferred Revenue" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "deferred_revenue_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Revenue Account", - "options": "Account" - }, - { - "default": "0", - "fieldname": "enable_deferred_revenue", - "fieldtype": "Check", - "label": "Enable Deferred Revenue" - }, - { - "fieldname": "column_break_85", - "fieldtype": "Column Break" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "no_of_months", - "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "deferred_expense_section", - "fieldtype": "Section Break", - "label": "Deferred Expense" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "deferred_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Expense Account", - "options": "Account" - }, - { - "default": "0", - "fieldname": "enable_deferred_expense", - "fieldtype": "Check", - "label": "Enable Deferred Expense" - }, - { - "fieldname": "column_break_88", - "fieldtype": "Column Break" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "no_of_months_exp", - "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "customer_details", - "fieldtype": "Section Break", - "label": "Customer Details" - }, - { - "fieldname": "customer_items", - "fieldtype": "Table", - "label": "Customer Items", - "options": "Item Customer Detail" - }, - { - "collapsible": 1, - "collapsible_depends_on": "taxes", - "fieldname": "item_tax_section_break", - "fieldtype": "Section Break", - "label": "Item Tax", - "oldfieldtype": "Section Break", - "options": "fa fa-money" - }, - { - "description": "Will also apply for variants", - "fieldname": "taxes", - "fieldtype": "Table", - "label": "Taxes", - "oldfieldname": "item_tax", - "oldfieldtype": "Table", - "options": "Item Tax" - }, - { - "collapsible": 1, - "fieldname": "inspection_criteria", - "fieldtype": "Section Break", - "label": "Inspection Criteria", - "oldfieldtype": "Section Break", - "options": "fa fa-search" - }, - { - "default": "0", - "fieldname": "inspection_required_before_purchase", - "fieldtype": "Check", - "label": "Inspection Required before Purchase", - "oldfieldname": "inspection_required", - "oldfieldtype": "Select" - }, - { - "default": "0", - "fieldname": "inspection_required_before_delivery", - "fieldtype": "Check", - "label": "Inspection Required before Delivery" - }, - { - "depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)", - "fieldname": "quality_inspection_template", - "fieldtype": "Link", - "label": "Quality Inspection Template", - "options": "Quality Inspection Template", - "print_hide": 1 - }, - { - "collapsible": 1, - "depends_on": "is_stock_item", - "fieldname": "manufacturing", - "fieldtype": "Section Break", - "label": "Manufacturing", - "oldfieldtype": "Section Break", - "options": "fa fa-cogs" - }, - { - "fieldname": "default_bom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default BOM", - "no_copy": 1, - "oldfieldname": "default_bom", - "oldfieldtype": "Link", - "options": "BOM", - "read_only": 1 - }, - { - "default": "0", - "description": "If subcontracted to a vendor", - "fieldname": "is_sub_contracted_item", - "fieldtype": "Check", - "label": "Supply Raw Materials for Purchase", - "oldfieldname": "is_sub_contracted_item", - "oldfieldtype": "Select" - }, - { - "fieldname": "column_break_74", - "fieldtype": "Column Break" - }, - { - "fieldname": "customer_code", - "fieldtype": "Data", - "hidden": 1, - "label": "Customer Code", - "no_copy": 1, - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "website_section", - "fieldtype": "Section Break", - "label": "Website", - "options": "fa fa-globe" - }, - { - "default": "0", - "depends_on": "eval:!doc.variant_of", - "fieldname": "show_in_website", - "fieldtype": "Check", - "label": "Show in Website", - "search_index": 1 - }, - { - "default": "0", - "depends_on": "variant_of", - "fieldname": "show_variant_in_website", - "fieldtype": "Check", - "label": "Show in Website (Variant)", - "search_index": 1 - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "route", - "fieldtype": "Small Text", - "label": "Route", - "no_copy": 1 - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Items with higher weightage will be shown higher", - "fieldname": "weightage", - "fieldtype": "Int", - "label": "Weightage" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Show a slideshow at the top of the page", - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Slideshow", - "options": "Website Slideshow" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Item Image (if not slideshow)", - "fieldname": "website_image", - "fieldtype": "Attach", - "label": "Website Image" - }, - { - "fieldname": "thumbnail", - "fieldtype": "Data", - "label": "Thumbnail", - "read_only": 1 - }, - { - "fieldname": "cb72", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.", - "fieldname": "website_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Website Warehouse", - "options": "Warehouse" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "List this Item in multiple groups on the website.", - "fieldname": "website_item_groups", - "fieldtype": "Table", - "label": "Website Item Groups", - "options": "Website Item Group" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "set_meta_tags", - "fieldtype": "Button", - "label": "Set Meta Tags" - }, - { - "collapsible": 1, - "collapsible_depends_on": "website_specifications", - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "sb72", - "fieldtype": "Section Break", - "label": "Website Specifications" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "copy_from_item_group", - "fieldtype": "Button", - "label": "Copy From Item Group" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "website_specifications", - "fieldtype": "Table", - "label": "Website Specifications", - "options": "Item Website Specification" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "web_long_description", - "fieldtype": "Text Editor", - "label": "Website Description" - }, - { - "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.", - "fieldname": "website_content", - "fieldtype": "HTML Editor", - "label": "Website Content" - }, - { - "fieldname": "total_projected_qty", - "fieldtype": "Float", - "hidden": 1, - "label": "Total Projected Qty", - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "eval:(!doc.is_item_from_hub)", - "fieldname": "hub_publishing_sb", - "fieldtype": "Section Break", - "label": "Hub Publishing Details" - }, - { - "default": "0", - "description": "Publish Item to hub.erpnext.com", - "fieldname": "publish_in_hub", - "fieldtype": "Check", - "label": "Publish in Hub" - }, - { - "fieldname": "hub_category_to_publish", - "fieldtype": "Data", - "label": "Hub Category to Publish", - "read_only": 1 - }, - { - "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", - "fieldname": "hub_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Hub Warehouse", - "options": "Warehouse" - }, - { - "default": "0", - "fieldname": "synced_with_hub", - "fieldtype": "Check", - "label": "Synced With Hub", - "read_only": 1 - }, - { - "fieldname": "manufacturers", - "fieldtype": "Table", - "label": "Manufacturers", - "options": "Item Manufacturer" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "over_delivery_receipt_allowance", - "fieldtype": "Float", - "label": "Over Delivery/Receipt Allowance (%)", - "oldfieldname": "tolerance", - "oldfieldtype": "Currency" - }, - { - "fieldname": "over_billing_allowance", - "fieldtype": "Float", - "label": "Over Billing Allowance (%)", - "depends_on": "eval:!doc.__islocal" - } - ], - "has_web_view": 1, - "icon": "fa fa-tag", - "idx": 2, - "image_field": "image", - "max_attachments": 1, - "modified": "2019-09-03 18:34:13.977931", - "modified_by": "Administrator", - "module": "Stock", - "name": "Item", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Item Manager", - "share": 1, - "write": 1 - }, - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager" - }, - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User" - }, - { - "read": 1, - "role": "Sales User" - }, - { - "read": 1, - "role": "Purchase User" - }, - { - "read": 1, - "role": "Maintenance User" - }, - { - "read": 1, - "role": "Accounts User" - }, - { - "read": 1, - "role": "Manufacturing User" - } - ], - "quick_entry": 1, - "search_fields": "item_name,description,item_group,customer_code", - "show_name_in_global_search": 1, - "show_preview_popup": 1, - "sort_field": "idx desc,modified desc", - "sort_order": "DESC", - "title_field": "item_name", - "track_changes": 1 - } + "allow_guest_to_view": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:item_code", + "creation": "2013-05-03 10:45:46", + "description": "A Product or a Service that is bought, sold or kept in stock.", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "name_and_description_section", + "naming_series", + "item_code", + "variant_of", + "item_name", + "item_group", + "is_item_from_hub", + "stock_uom", + "column_break0", + "disabled", + "allow_alternative_item", + "is_stock_item", + "include_item_in_manufacturing", + "opening_stock", + "valuation_rate", + "standard_rate", + "is_fixed_asset", + "auto_create_assets", + "asset_category", + "asset_naming_series", + "over_delivery_receipt_allowance", + "over_billing_allowance", + "image", + "section_break_11", + "brand", + "description", + "sb_barcodes", + "barcodes", + "inventory_section", + "shelf_life_in_days", + "end_of_life", + "default_material_request_type", + "valuation_method", + "column_break1", + "warranty_period", + "weight_per_unit", + "weight_uom", + "reorder_section", + "reorder_levels", + "unit_of_measure_conversion", + "uoms", + "serial_nos_and_batches", + "has_batch_no", + "create_new_batch", + "batch_number_series", + "has_expiry_date", + "retain_sample", + "sample_quantity", + "column_break_37", + "has_serial_no", + "serial_no_series", + "variants_section", + "has_variants", + "variant_based_on", + "attributes", + "defaults", + "item_defaults", + "purchase_details", + "is_purchase_item", + "purchase_uom", + "min_order_qty", + "safety_stock", + "purchase_details_cb", + "lead_time_days", + "last_purchase_rate", + "is_customer_provided_item", + "customer", + "supplier_details", + "delivered_by_supplier", + "column_break2", + "supplier_items", + "foreign_trade_details", + "country_of_origin", + "column_break_59", + "customs_tariff_number", + "sales_details", + "sales_uom", + "is_sales_item", + "column_break3", + "max_discount", + "deferred_revenue", + "deferred_revenue_account", + "enable_deferred_revenue", + "column_break_85", + "no_of_months", + "deferred_expense_section", + "deferred_expense_account", + "enable_deferred_expense", + "column_break_88", + "no_of_months_exp", + "customer_details", + "customer_items", + "item_tax_section_break", + "taxes", + "inspection_criteria", + "inspection_required_before_purchase", + "inspection_required_before_delivery", + "quality_inspection_template", + "manufacturing", + "default_bom", + "is_sub_contracted_item", + "column_break_74", + "customer_code", + "website_section", + "show_in_website", + "show_variant_in_website", + "route", + "weightage", + "slideshow", + "website_image", + "thumbnail", + "cb72", + "website_warehouse", + "website_item_groups", + "set_meta_tags", + "sb72", + "copy_from_item_group", + "website_specifications", + "web_long_description", + "website_content", + "total_projected_qty", + "hub_publishing_sb", + "publish_in_hub", + "hub_category_to_publish", + "hub_warehouse", + "synced_with_hub", + "manufacturers" + ], + "fields": [ + { + "fieldname": "name_and_description_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-flag" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "STO-ITEM-.YYYY.-", + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "item_code", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, + { + "depends_on": "variant_of", + "description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified", + "fieldname": "variant_of", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Variant Of", + "options": "Item", + "read_only": 1, + "search_index": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "search_index": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "in_list_view": 1, + "in_preview": 1, + "in_standard_filter": 1, + "label": "Item Group", + "oldfieldname": "item_group", + "oldfieldtype": "Link", + "options": "Item Group", + "reqd": 1, + "search_index": 1 + }, + { + "default": "0", + "fieldname": "is_item_from_hub", + "fieldtype": "Check", + "label": "Is Item from Hub", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Unit of Measure", + "oldfieldname": "stock_uom", + "oldfieldtype": "Link", + "options": "UOM", + "reqd": 1 + }, + { + "fieldname": "column_break0", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "default": "0", + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, + { + "bold": 1, + "default": "1", + "fieldname": "is_stock_item", + "fieldtype": "Check", + "label": "Maintain Stock", + "oldfieldname": "is_stock_item", + "oldfieldtype": "Select" + }, + { + "default": "1", + "fieldname": "include_item_in_manufacturing", + "fieldtype": "Check", + "label": "Include Item In Manufacturing" + }, + { + "bold": 1, + "depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)", + "fieldname": "opening_stock", + "fieldtype": "Float", + "label": "Opening Stock" + }, + { + "depends_on": "is_stock_item", + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "label": "Valuation Rate" + }, + { + "bold": 1, + "depends_on": "eval:doc.__islocal", + "fieldname": "standard_rate", + "fieldtype": "Currency", + "label": "Standard Selling Rate" + }, + { + "default": "0", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "label": "Is Fixed Asset", + "set_only_once": 1 + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_category", + "fieldtype": "Link", + "label": "Asset Category", + "options": "Asset Category" + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_naming_series", + "fieldtype": "Select", + "label": "Asset Naming Series" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "in_preview": 1, + "label": "Image", + "options": "image", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "brand", + "fieldtype": "Link", + "label": "Brand", + "oldfieldname": "brand", + "oldfieldtype": "Link", + "options": "Brand", + "print_hide": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "in_preview": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text" + }, + { + "fieldname": "sb_barcodes", + "fieldtype": "Section Break", + "label": "Barcodes" + }, + { + "fieldname": "barcodes", + "fieldtype": "Table", + "label": "Barcodes", + "options": "Item Barcode" + }, + { + "collapsible": 1, + "collapsible_depends_on": "is_stock_item", + "depends_on": "is_stock_item", + "fieldname": "inventory_section", + "fieldtype": "Section Break", + "label": "Inventory", + "oldfieldtype": "Section Break", + "options": "fa fa-truck" + }, + { + "fieldname": "shelf_life_in_days", + "fieldtype": "Int", + "label": "Shelf Life In Days" + }, + { + "default": "2099-12-31", + "depends_on": "is_stock_item", + "fieldname": "end_of_life", + "fieldtype": "Date", + "label": "End of Life", + "oldfieldname": "end_of_life", + "oldfieldtype": "Date" + }, + { + "default": "Purchase", + "fieldname": "default_material_request_type", + "fieldtype": "Select", + "label": "Default Material Request Type", + "options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" + }, + { + "depends_on": "is_stock_item", + "fieldname": "valuation_method", + "fieldtype": "Select", + "label": "Valuation Method", + "options": "\nFIFO\nMoving Average", + "set_only_once": 1 + }, + { + "depends_on": "is_stock_item", + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "depends_on": "eval:doc.is_stock_item", + "fieldname": "warranty_period", + "fieldtype": "Data", + "label": "Warranty Period (in days)", + "oldfieldname": "warranty_period", + "oldfieldtype": "Data" + }, + { + "depends_on": "is_stock_item", + "fieldname": "weight_per_unit", + "fieldtype": "Float", + "label": "Weight Per Unit" + }, + { + "depends_on": "eval:doc.is_stock_item", + "fieldname": "weight_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Weight UOM", + "options": "UOM" + }, + { + "collapsible": 1, + "depends_on": "is_stock_item", + "fieldname": "reorder_section", + "fieldtype": "Section Break", + "label": "Auto re-order", + "options": "fa fa-rss" + }, + { + "description": "Will also apply for variants unless overrridden", + "fieldname": "reorder_levels", + "fieldtype": "Table", + "label": "Reorder level based on Warehouse", + "options": "Item Reorder" + }, + { + "collapsible": 1, + "fieldname": "unit_of_measure_conversion", + "fieldtype": "Section Break", + "label": "Units of Measure" + }, + { + "description": "Will also apply for variants", + "fieldname": "uoms", + "fieldtype": "Table", + "label": "UOMs", + "oldfieldname": "uom_conversion_details", + "oldfieldtype": "Table", + "options": "UOM Conversion Detail" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset", + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "fieldname": "serial_nos_and_batches", + "fieldtype": "Section Break", + "label": "Serial Nos and Batches" + }, + { + "default": "0", + "depends_on": "eval:doc.is_stock_item", + "fieldname": "has_batch_no", + "fieldtype": "Check", + "label": "Has Batch No", + "no_copy": 1, + "oldfieldname": "has_batch_no", + "oldfieldtype": "Select" + }, + { + "default": "0", + "depends_on": "has_batch_no", + "fieldname": "create_new_batch", + "fieldtype": "Check", + "label": "Automatically Create New Batch" + }, + { + "depends_on": "eval:doc.has_batch_no==1 && doc.create_new_batch==1", + "description": "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.", + "fieldname": "batch_number_series", + "fieldtype": "Data", + "label": "Batch Number Series", + "translatable": 1 + }, + { + "default": "0", + "depends_on": "has_batch_no", + "fieldname": "has_expiry_date", + "fieldtype": "Check", + "label": "Has Expiry Date" + }, + { + "default": "0", + "fieldname": "retain_sample", + "fieldtype": "Check", + "label": "Retain Sample" + }, + { + "depends_on": "eval: (doc.retain_sample && doc.has_batch_no)", + "description": "Maximum sample quantity that can be retained", + "fieldname": "sample_quantity", + "fieldtype": "Int", + "label": "Max Sample Quantity" + }, + { + "fieldname": "column_break_37", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "fieldname": "has_serial_no", + "fieldtype": "Check", + "label": "Has Serial No", + "no_copy": 1, + "oldfieldname": "has_serial_no", + "oldfieldtype": "Select" + }, + { + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.", + "fieldname": "serial_no_series", + "fieldtype": "Data", + "label": "Serial Number Series" + }, + { + "collapsible": 1, + "collapsible_depends_on": "attributes", + "fieldname": "variants_section", + "fieldtype": "Section Break", + "label": "Variants" + }, + { + "default": "0", + "depends_on": "eval:!doc.variant_of", + "description": "If this item has variants, then it cannot be selected in sales orders etc.", + "fieldname": "has_variants", + "fieldtype": "Check", + "in_standard_filter": 1, + "label": "Has Variants", + "no_copy": 1 + }, + { + "default": "Item Attribute", + "depends_on": "has_variants", + "fieldname": "variant_based_on", + "fieldtype": "Select", + "label": "Variant Based On", + "options": "Item Attribute\nManufacturer" + }, + { + "depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", + "fieldname": "attributes", + "fieldtype": "Table", + "hidden": 1, + "label": "Attributes", + "no_copy": 1, + "options": "Item Variant Attribute" + }, + { + "fieldname": "defaults", + "fieldtype": "Section Break", + "label": "Sales, Purchase, Accounting Defaults" + }, + { + "fieldname": "item_defaults", + "fieldtype": "Table", + "label": "Item Defaults", + "options": "Item Default" + }, + { + "collapsible": 1, + "fieldname": "purchase_details", + "fieldtype": "Section Break", + "label": "Purchase, Replenishment Details", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "default": "1", + "fieldname": "is_purchase_item", + "fieldtype": "Check", + "label": "Is Purchase Item" + }, + { + "fieldname": "purchase_uom", + "fieldtype": "Link", + "label": "Default Purchase Unit of Measure", + "options": "UOM" + }, + { + "default": "0.00", + "depends_on": "is_stock_item", + "fieldname": "min_order_qty", + "fieldtype": "Float", + "label": "Minimum Order Qty", + "oldfieldname": "min_order_qty", + "oldfieldtype": "Currency" + }, + { + "fieldname": "safety_stock", + "fieldtype": "Float", + "label": "Safety Stock" + }, + { + "fieldname": "purchase_details_cb", + "fieldtype": "Column Break" + }, + { + "description": "Average time taken by the supplier to deliver", + "fieldname": "lead_time_days", + "fieldtype": "Int", + "label": "Lead Time in days", + "oldfieldname": "lead_time_days", + "oldfieldtype": "Int" + }, + { + "fieldname": "last_purchase_rate", + "fieldtype": "Float", + "label": "Last Purchase Rate", + "no_copy": 1, + "oldfieldname": "last_purchase_rate", + "oldfieldtype": "Currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_customer_provided_item", + "fieldtype": "Check", + "label": "Is Customer Provided Item" + }, + { + "depends_on": "eval:doc.is_customer_provided_item==1", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "collapsible": 1, + "fieldname": "supplier_details", + "fieldtype": "Section Break", + "label": "Supplier Details" + }, + { + "default": "0", + "fieldname": "delivered_by_supplier", + "fieldtype": "Check", + "label": "Delivered by Supplier (Drop Ship)", + "print_hide": 1 + }, + { + "fieldname": "column_break2", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "supplier_items", + "fieldtype": "Table", + "label": "Supplier Items", + "options": "Item Supplier" + }, + { + "collapsible": 1, + "fieldname": "foreign_trade_details", + "fieldtype": "Section Break", + "label": "Foreign Trade Details" + }, + { + "fieldname": "country_of_origin", + "fieldtype": "Link", + "label": "Country of Origin", + "options": "Country" + }, + { + "fieldname": "column_break_59", + "fieldtype": "Column Break" + }, + { + "fieldname": "customs_tariff_number", + "fieldtype": "Link", + "label": "Customs Tariff Number", + "options": "Customs Tariff Number" + }, + { + "collapsible": 1, + "fieldname": "sales_details", + "fieldtype": "Section Break", + "label": "Sales Details", + "oldfieldtype": "Section Break", + "options": "fa fa-tag" + }, + { + "fieldname": "sales_uom", + "fieldtype": "Link", + "label": "Default Sales Unit of Measure", + "options": "UOM" + }, + { + "default": "1", + "fieldname": "is_sales_item", + "fieldtype": "Check", + "label": "Is Sales Item" + }, + { + "fieldname": "column_break3", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "max_discount", + "fieldtype": "Float", + "label": "Max Discount (%)", + "oldfieldname": "max_discount", + "oldfieldtype": "Currency" + }, + { + "collapsible": 1, + "fieldname": "deferred_revenue", + "fieldtype": "Section Break", + "label": "Deferred Revenue" + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Deferred Revenue Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "enable_deferred_revenue", + "fieldtype": "Check", + "label": "Enable Deferred Revenue" + }, + { + "fieldname": "column_break_85", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "no_of_months", + "fieldtype": "Int", + "label": "No of Months" + }, + { + "collapsible": 1, + "fieldname": "deferred_expense_section", + "fieldtype": "Section Break", + "label": "Deferred Expense" + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Deferred Expense Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "enable_deferred_expense", + "fieldtype": "Check", + "label": "Enable Deferred Expense" + }, + { + "fieldname": "column_break_88", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "no_of_months_exp", + "fieldtype": "Int", + "label": "No of Months" + }, + { + "collapsible": 1, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_items", + "fieldtype": "Table", + "label": "Customer Items", + "options": "Item Customer Detail" + }, + { + "collapsible": 1, + "collapsible_depends_on": "taxes", + "fieldname": "item_tax_section_break", + "fieldtype": "Section Break", + "label": "Item Tax", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, + { + "description": "Will also apply for variants", + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes", + "oldfieldname": "item_tax", + "oldfieldtype": "Table", + "options": "Item Tax" + }, + { + "collapsible": 1, + "fieldname": "inspection_criteria", + "fieldtype": "Section Break", + "label": "Inspection Criteria", + "oldfieldtype": "Section Break", + "options": "fa fa-search" + }, + { + "default": "0", + "fieldname": "inspection_required_before_purchase", + "fieldtype": "Check", + "label": "Inspection Required before Purchase", + "oldfieldname": "inspection_required", + "oldfieldtype": "Select" + }, + { + "default": "0", + "fieldname": "inspection_required_before_delivery", + "fieldtype": "Check", + "label": "Inspection Required before Delivery" + }, + { + "depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)", + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template", + "print_hide": 1 + }, + { + "collapsible": 1, + "depends_on": "is_stock_item", + "fieldname": "manufacturing", + "fieldtype": "Section Break", + "label": "Manufacturing", + "oldfieldtype": "Section Break", + "options": "fa fa-cogs" + }, + { + "fieldname": "default_bom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default BOM", + "no_copy": 1, + "oldfieldname": "default_bom", + "oldfieldtype": "Link", + "options": "BOM", + "read_only": 1 + }, + { + "default": "0", + "description": "If subcontracted to a vendor", + "fieldname": "is_sub_contracted_item", + "fieldtype": "Check", + "label": "Supply Raw Materials for Purchase", + "oldfieldname": "is_sub_contracted_item", + "oldfieldtype": "Select" + }, + { + "fieldname": "column_break_74", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer_code", + "fieldtype": "Data", + "hidden": 1, + "label": "Customer Code", + "no_copy": 1, + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "website_section", + "fieldtype": "Section Break", + "label": "Website", + "options": "fa fa-globe" + }, + { + "default": "0", + "depends_on": "eval:!doc.variant_of", + "fieldname": "show_in_website", + "fieldtype": "Check", + "label": "Show in Website", + "search_index": 1 + }, + { + "default": "0", + "depends_on": "variant_of", + "fieldname": "show_variant_in_website", + "fieldtype": "Check", + "label": "Show in Website (Variant)", + "search_index": 1 + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "route", + "fieldtype": "Small Text", + "label": "Route", + "no_copy": 1 + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Items with higher weightage will be shown higher", + "fieldname": "weightage", + "fieldtype": "Int", + "label": "Weightage" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Show a slideshow at the top of the page", + "fieldname": "slideshow", + "fieldtype": "Link", + "label": "Slideshow", + "options": "Website Slideshow" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Item Image (if not slideshow)", + "fieldname": "website_image", + "fieldtype": "Attach", + "label": "Website Image" + }, + { + "fieldname": "thumbnail", + "fieldtype": "Data", + "label": "Thumbnail", + "read_only": 1 + }, + { + "fieldname": "cb72", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.", + "fieldname": "website_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Website Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "List this Item in multiple groups on the website.", + "fieldname": "website_item_groups", + "fieldtype": "Table", + "label": "Website Item Groups", + "options": "Website Item Group" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "set_meta_tags", + "fieldtype": "Button", + "label": "Set Meta Tags" + }, + { + "collapsible": 1, + "collapsible_depends_on": "website_specifications", + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "sb72", + "fieldtype": "Section Break", + "label": "Website Specifications" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "copy_from_item_group", + "fieldtype": "Button", + "label": "Copy From Item Group" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "website_specifications", + "fieldtype": "Table", + "label": "Website Specifications", + "options": "Item Website Specification" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "web_long_description", + "fieldtype": "Text Editor", + "label": "Website Description" + }, + { + "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.", + "fieldname": "website_content", + "fieldtype": "HTML Editor", + "label": "Website Content" + }, + { + "fieldname": "total_projected_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Total Projected Qty", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:(!doc.is_item_from_hub)", + "fieldname": "hub_publishing_sb", + "fieldtype": "Section Break", + "label": "Hub Publishing Details" + }, + { + "default": "0", + "description": "Publish Item to hub.erpnext.com", + "fieldname": "publish_in_hub", + "fieldtype": "Check", + "label": "Publish in Hub" + }, + { + "fieldname": "hub_category_to_publish", + "fieldtype": "Data", + "label": "Hub Category to Publish", + "read_only": 1 + }, + { + "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", + "fieldname": "hub_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Hub Warehouse", + "options": "Warehouse" + }, + { + "default": "0", + "fieldname": "synced_with_hub", + "fieldtype": "Check", + "label": "Synced With Hub", + "read_only": 1 + }, + { + "fieldname": "manufacturers", + "fieldtype": "Table", + "label": "Manufacturers", + "options": "Item Manufacturer" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "over_delivery_receipt_allowance", + "fieldtype": "Float", + "label": "Over Delivery/Receipt Allowance (%)", + "oldfieldname": "tolerance", + "oldfieldtype": "Currency" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "over_billing_allowance", + "fieldtype": "Float", + "label": "Over Billing Allowance (%)" + }, + { + "default": "0", + "depends_on": "is_fixed_asset", + "fieldname": "auto_create_assets", + "fieldtype": "Check", + "label": "Auto Create Assets on Purchase" + } + ], + "has_web_view": 1, + "icon": "fa fa-tag", + "idx": 2, + "image_field": "image", + "max_attachments": 1, + "modified": "2019-10-09 17:05:59.576119", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Item Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager" + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User" + }, + { + "read": 1, + "role": "Sales User" + }, + { + "read": 1, + "role": "Purchase User" + }, + { + "read": 1, + "role": "Maintenance User" + }, + { + "read": 1, + "role": "Accounts User" + }, + { + "read": 1, + "role": "Manufacturing User" + } + ], + "quick_entry": 1, + "search_fields": "item_name,description,item_group,customer_code", + "show_name_in_global_search": 1, + "show_preview_popup": 1, + "sort_field": "idx desc,modified desc", + "sort_order": "DESC", + "title_field": "item_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json index 66c33a1c9d..90a392c145 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json @@ -13,6 +13,7 @@ "qty", "rate", "amount", + "is_fixed_asset", "applicable_charges", "purchase_receipt_item", "accounting_dimensions_section", @@ -119,14 +120,25 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "0", + "fetch_from": "item_code.is_fixed_asset", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Fixed Asset", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-05-26 09:48:15.569956", + "modified": "2019-11-12 15:41:21.053462", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Item", "owner": "wasim@webnotestech.com", - "permissions": [] + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index c9a3fd976f..5de1352518 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -129,6 +129,10 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ }, distribute_charges_based_on: function (frm) { this.set_applicable_charges_for_item(); + }, + + items_remove: () => { + this.trigger('set_applicable_charges_for_item'); } }); diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json index c2c669211a..46fdc8fc10 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -1,545 +1,149 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2014-07-11 11:33:42.547339", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "autoname": "naming_series:", + "creation": "2014-07-11 11:33:42.547339", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "company", + "purchase_receipts", + "sec_break1", + "taxes", + "purchase_receipt_items", + "get_items_from_purchase_receipts", + "items", + "section_break_9", + "total_taxes_and_charges", + "col_break1", + "distribute_charges_based_on", + "amended_from", + "sec_break2", + "landed_cost_help" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "options": "MAT-LCV-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-LCV-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_receipts", - "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": "Purchase Receipts", - "length": 0, - "no_copy": 0, - "options": "Landed Cost Purchase Receipt", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "purchase_receipts", + "fieldtype": "Table", + "label": "Purchase Receipts", + "options": "Landed Cost Purchase Receipt", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_receipt_items", - "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": "Purchase Receipt Items", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "purchase_receipt_items", + "fieldtype": "Section Break", + "label": "Purchase Receipt Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "get_items_from_purchase_receipts", - "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": "Get Items From Purchase Receipts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "get_items_from_purchase_receipts", + "fieldtype": "Button", + "label": "Get Items From Purchase Receipts" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "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": "Purchase Receipt Items", - "length": 0, - "no_copy": 1, - "options": "Landed Cost Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Purchase Receipt Items", + "no_copy": 1, + "options": "Landed Cost Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_break1", - "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": "Additional Charges", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sec_break1", + "fieldtype": "Section Break", + "label": "Applicable Charges" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes", - "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": "Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "Landed Cost Taxes and Charges", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes and Charges", + "options": "Landed Cost Taxes and Charges", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "Company:company:default_currency", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "distribute_charges_based_on", - "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": "Distribute Charges Based On", - "length": 0, - "no_copy": 0, - "options": "\nQty\nAmount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "distribute_charges_based_on", + "fieldtype": "Select", + "label": "Distribute Charges Based On", + "options": "Qty\nAmount", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 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": "Landed Cost Voucher", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Landed Cost Voucher", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_break2", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sec_break2", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "landed_cost_help", - "fieldtype": "HTML", - "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": "Landed Cost Help", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "landed_cost_help", + "fieldtype": "HTML", + "label": "Landed Cost Help" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-usd", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:30.850736", - "modified_by": "Administrator", - "module": "Stock", - "name": "Landed Cost Voucher", - "name_case": "", - "owner": "Administrator", + ], + "icon": "icon-usd", + "is_submittable": 1, + "modified": "2019-10-09 13:39:36.082777", + "modified_by": "Administrator", + "module": "Stock", + "name": "Landed Cost Voucher", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "export": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, "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, - "track_views": 0 + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 3f370935ef..173b394f79 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -16,16 +16,13 @@ class LandedCostVoucher(Document): if pr.receipt_document_type and pr.receipt_document: pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description, pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name, - pr_item.cost_center, pr_item.asset + pr_item.cost_center, pr_item.is_fixed_asset from `tab{doctype} Item` pr_item where parent = %s and exists(select name from tabItem where name = pr_item.item_code and (is_stock_item = 1 or is_fixed_asset=1)) """.format(doctype=pr.receipt_document_type), pr.receipt_document, as_dict=True) for d in pr_items: - if d.asset and frappe.db.get_value("Asset", d.asset, 'docstatus') == 1: - continue - item = self.append("items") item.item_code = d.item_code item.description = d.description @@ -37,15 +34,16 @@ class LandedCostVoucher(Document): item.receipt_document_type = pr.receipt_document_type item.receipt_document = pr.receipt_document item.purchase_receipt_item = d.name + item.is_fixed_asset = d.is_fixed_asset def validate(self): self.check_mandatory() - self.validate_purchase_receipts() - self.set_total_taxes_and_charges() if not self.get("items"): self.get_items_from_purchase_receipts() else: self.validate_applicable_charges_for_item() + self.validate_purchase_receipts() + self.set_total_taxes_and_charges() def check_mandatory(self): if not self.get("purchase_receipts"): @@ -64,6 +62,7 @@ class LandedCostVoucher(Document): for item in self.get("items"): if not item.receipt_document: frappe.throw(_("Item must be added using 'Get Items from Purchase Receipts' button")) + elif item.receipt_document not in receipt_documents: frappe.throw(_("Item Row {0}: {1} {2} does not exist in above '{1}' table") .format(item.idx, item.receipt_document_type, item.receipt_document)) @@ -96,8 +95,6 @@ class LandedCostVoucher(Document): else: frappe.throw(_("Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges")) - - def on_submit(self): self.update_landed_cost() @@ -107,6 +104,9 @@ class LandedCostVoucher(Document): def update_landed_cost(self): for d in self.get("purchase_receipts"): doc = frappe.get_doc(d.receipt_document_type, d.receipt_document) + + # check if there are {qty} assets created and linked to this receipt document + self.validate_asset_qty_and_status(d.receipt_document_type, doc) # set landed cost voucher amount in pr item doc.set_landed_cost_voucher_amount() @@ -118,23 +118,42 @@ class LandedCostVoucher(Document): for item in doc.get("items"): item.db_update() + # asset rate will be updated while creating asset gl entries from PI or PY + # update latest valuation rate in serial no - self.update_rate_in_serial_no(doc) + self.update_rate_in_serial_no_for_non_asset_items(doc) # update stock & gl entries for cancelled state of PR doc.docstatus = 2 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.make_gl_entries_on_cancel(repost_future_gle=False) - # update stock & gl entries for submit state of PR doc.docstatus = 1 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.make_gl_entries() - def update_rate_in_serial_no(self, receipt_document): + def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): + for item in self.get('items'): + if item.is_fixed_asset: + receipt_document_type = 'purchase_invoice' if item.receipt_document_type == 'Purchase Invoice' \ + else 'purchase_receipt' + docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document }, + fields=['name', 'docstatus']) + if not docs or len(docs) != item.qty: + frappe.throw(_('There are not enough asset created or linked to {0}. \ + Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty)) + if docs: + for d in docs: + if d.docstatus == 1: + frappe.throw(_('{2} {0} has submitted Assets.\ + Remove Item {1} from table to continue.').format( + item.receipt_document, item.item_code, item.receipt_document_type) + ) + + def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): for item in receipt_document.get("items"): - if item.serial_no: + if not item.is_fixed_asset and item.serial_no: serial_nos = get_serial_nos(item.serial_no) if serial_nos: frappe.db.sql("update `tabSerial No` set purchase_rate=%s where name in ({0})" diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index aef53ed74b..d5914f9b28 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -6,27 +6,35 @@ frappe.provide("erpnext.stock"); frappe.ui.form.on("Purchase Receipt", { - setup: function(frm) { + setup: (frm) => { + frm.make_methods = { + 'Landed Cost Voucher': () => { + let lcv = frappe.model.get_new_doc('Landed Cost Voucher'); + lcv.company = frm.doc.company; + + let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Receipt'); + lcv_receipt.receipt_document_type = 'Purchase Receipt'; + lcv_receipt.receipt_document = frm.doc.name; + lcv_receipt.supplier = frm.doc.supplier; + lcv_receipt.grand_total = frm.doc.grand_total; + lcv.purchase_receipts = [lcv_receipt]; + + frappe.set_route("Form", lcv.doctype, lcv.name); + }, + } + frm.custom_make_buttons = { 'Stock Entry': 'Return', 'Purchase Invoice': 'Invoice' }; - frm.set_query("asset", "items", function() { - return { - filters: { - "purchase_receipt": frm.doc.name - } - } - }); - frm.set_query("expense_account", "items", function() { return { query: "erpnext.controllers.queries.get_expense_account", - filters: {'company': frm.doc.company} + filters: {'company': frm.doc.company } } }); - + }, onload: function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { @@ -57,7 +65,7 @@ frappe.ui.form.on("Purchase Receipt", { toggle_display_account_head: function(frm) { var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company) frm.fields_dict["items"].grid.set_column_disp(["cost_center"], enabled); - }, + } }); erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({ diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1bfdca50ea..56fd47e1bc 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -281,7 +281,7 @@ class PurchaseReceipt(BuyingController): d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) - self.get_asset_gl_entry(gl_entries, expenses_included_in_valuation) + self.get_asset_gl_entry(gl_entries) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): @@ -335,81 +335,85 @@ class PurchaseReceipt(BuyingController): return process_gl_map(gl_entries) - def get_asset_gl_entry(self, gl_entries, expenses_included_in_valuation=None): - arbnb_account, cwip_account = None, None - - if not expenses_included_in_valuation: - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - - for d in self.get("items"): - asset_category = frappe.get_cached_value("Item", d.item_code, "asset_category") - cwip_enabled = is_cwip_accounting_enabled(self.company, asset_category) - - if d.is_fixed_asset and not (arbnb_account and cwip_account): - arbnb_account = self.get_company_default("asset_received_but_not_billed") - - # CWIP entry - cwip_account = get_asset_account("capital_work_in_progress_account", d.asset, - company = self.company) - - if d.is_fixed_asset and cwip_enabled: - asset_amount = flt(d.net_amount) + flt(d.item_tax_amount/self.conversion_rate) - base_asset_amount = flt(d.base_net_amount + d.item_tax_amount) - - cwip_account_currency = get_account_currency(cwip_account) - gl_entries.append(self.get_gl_dict({ - "account": cwip_account, - "against": arbnb_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": (base_asset_amount - if cwip_account_currency == self.company_currency else asset_amount) - }, item=d)) - - # Asset received but not billed - asset_rbnb_currency = get_account_currency(arbnb_account) - gl_entries.append(self.get_gl_dict({ - "account": arbnb_account, - "against": cwip_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "credit": base_asset_amount, - "credit_in_account_currency": (base_asset_amount - if asset_rbnb_currency == self.company_currency else asset_amount) - }, item=d)) - - if d.is_fixed_asset and flt(d.landed_cost_voucher_amount): - asset_account = (get_asset_category_account(d.asset, 'fixed_asset_account', - company = self.company) if not cwip_enabled else cwip_account) - - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_valuation, - "against": asset_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(d.landed_cost_voucher_amount), - "project": d.project - }, item=d)) - - gl_entries.append(self.get_gl_dict({ - "account": asset_account, - "against": expenses_included_in_valuation, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(d.landed_cost_voucher_amount), - "project": d.project - }, item=d)) - - if d.asset: - doc = frappe.get_doc("Asset", d.asset) - frappe.db.set_value("Asset", d.asset, "gross_purchase_amount", - doc.gross_purchase_amount + flt(d.landed_cost_voucher_amount)) - - frappe.db.set_value("Asset", d.asset, "purchase_receipt_amount", - doc.purchase_receipt_amount + flt(d.landed_cost_voucher_amount)) - + def get_asset_gl_entry(self, gl_entries): + for item in self.get("items"): + if item.is_fixed_asset: + if is_cwip_accounting_enabled(self.company, item.asset_category): + self.add_asset_gl_entries(item, gl_entries) + if flt(item.landed_cost_voucher_amount): + self.add_lcv_gl_entries(item, gl_entries) + # update assets gross amount by its valuation rate + # valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item + self.update_assets(item, item.valuation_rate) return gl_entries + + def add_asset_gl_entries(self, item, gl_entries): + arbnb_account = self.get_company_default("asset_received_but_not_billed") + # This returns company's default cwip account + cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) + + asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) + base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) + + cwip_account_currency = get_account_currency(cwip_account) + # debit cwip account + gl_entries.append(self.get_gl_dict({ + "account": cwip_account, + "against": arbnb_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": base_asset_amount, + "debit_in_account_currency": (base_asset_amount + if cwip_account_currency == self.company_currency else asset_amount) + }, item=item)) + + asset_rbnb_currency = get_account_currency(arbnb_account) + # credit arbnb account + gl_entries.append(self.get_gl_dict({ + "account": arbnb_account, + "against": cwip_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "credit": base_asset_amount, + "credit_in_account_currency": (base_asset_amount + if asset_rbnb_currency == self.company_currency else asset_amount) + }, item=item)) + + def add_lcv_gl_entries(self, item, gl_entries): + expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") + if not is_cwip_accounting_enabled(self.company, item.asset_category): + asset_account = get_asset_category_account(asset_category=item.asset_category, \ + fieldname='fixed_asset_account', company=self.company) + else: + # This returns company's default cwip account + asset_account = get_asset_account("capital_work_in_progress_account", company=self.company) + + gl_entries.append(self.get_gl_dict({ + "account": expenses_included_in_asset_valuation, + "against": asset_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + gl_entries.append(self.get_gl_dict({ + "account": asset_account, + "against": expenses_included_in_asset_valuation, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + def update_assets(self, item, valuation_rate): + assets = frappe.db.get_all('Asset', + filters={ 'purchase_receipt': self.name, 'item_code': item.item_code } + ) + + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate)) def update_status(self, status): self.set_status(update=True, status = status) @@ -517,7 +521,8 @@ def make_purchase_invoice(source_name, target_doc=None): "purchase_order_item": "po_detail", "purchase_order": "purchase_order", "is_fixed_asset": "is_fixed_asset", - "asset": "asset", + "asset_location": "asset_location", + "asset_category": 'asset_category' }, "postprocess": update_item, "filter": lambda d: get_pending_qty(d)[0] <= 0 if not doc.get("is_return") else get_pending_qty(d)[0] > 0 diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e9ddf9d976..2afb69353f 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -281,8 +281,8 @@ class TestPurchaseReceipt(unittest.TestCase): serial_no=serial_no, basic_rate=100, do_not_submit=True) self.assertRaises(SerialNoDuplicateError, se.submit) - def test_serialized_asset_item(self): - asset_item = "Test Serialized Asset Item" + def test_auto_asset_creation(self): + asset_item = "Test Asset Item" if not frappe.db.exists('Item', asset_item): asset_category = frappe.get_all('Asset Category') @@ -308,30 +308,18 @@ class TestPurchaseReceipt(unittest.TestCase): asset_category = doc.name item_data = make_item(asset_item, {'is_stock_item':0, - 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, - 'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) + 'stock_uom': 'Box', 'is_fixed_asset': 1, 'auto_create_assets': 1, + 'asset_category': asset_category, 'asset_naming_series': 'ABC.###'}) asset_item = item_data.item_code pr = make_purchase_receipt(item_code=asset_item, qty=3) - asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') - asset_movement = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'name') - serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') + assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) - self.assertEquals(len(serial_nos), 3) + self.assertEquals(len(assets), 3) - location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location') + location = frappe.db.get_value('Asset', assets[0].name, 'location') self.assertEquals(location, "Test Location") - frappe.db.set_value("Asset", asset, "purchase_receipt", "") - frappe.db.set_value("Purchase Receipt Item", pr.items[0].name, "asset", "") - - pr.load_from_db() - - pr.cancel() - serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] - self.assertEquals(len(serial_nos), 0) - frappe.db.sql("delete from `tabAsset`") - def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') @@ -534,8 +522,10 @@ def make_purchase_receipt(**args): received_qty = args.received_qty or qty rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty) + item_code = args.item or args.item_code or "_Test Item" + uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM" pr.append("items", { - "item_code": args.item or args.item_code or "_Test Item", + "item_code": item_code, "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": qty, "received_qty": received_qty, @@ -545,7 +535,7 @@ def make_purchase_receipt(**args): "conversion_factor": args.conversion_factor or 1.0, "serial_no": args.serial_no, "stock_uom": args.stock_uom or "_Test UOM", - "uom": args.uom or "_Test UOM", + "uom": uom, "cost_center": args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center'), "asset_location": args.location or "Test Location" }) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 446a488a7e..16ec8db335 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -67,26 +67,26 @@ "warehouse_and_reference", "warehouse", "rejected_warehouse", - "quality_inspection", "purchase_order", "material_request", - "purchase_order_item", - "material_request_item", "column_break_40", "is_fixed_asset", - "asset", "asset_location", + "asset_category", "schedule_date", + "quality_inspection", "stock_qty", + "purchase_order_item", + "material_request_item", "section_break_45", + "allow_zero_valuation_rate", + "bom", + "col_break5", "serial_no", "batch_no", "column_break_48", "rejected_serial_no", "expense_account", - "col_break5", - "allow_zero_valuation_rate", - "bom", "include_exploded_items", "item_tax_rate", "accounting_dimensions_section", @@ -500,21 +500,6 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset", - "fieldtype": "Link", - "label": "Asset", - "no_copy": 1, - "options": "Asset" - }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset_location", - "fieldtype": "Link", - "label": "Asset Location", - "options": "Location" - }, { "fieldname": "purchase_order", "fieldtype": "Link", @@ -553,6 +538,7 @@ "fieldtype": "Section Break" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "serial_no", "fieldtype": "Small Text", "in_list_view": 1, @@ -562,10 +548,11 @@ "oldfieldtype": "Text" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "batch_no", "fieldtype": "Link", "in_list_view": 1, - "label": "Batch No", + "label": "Batch No!", "no_copy": 1, "oldfieldname": "batch_no", "oldfieldtype": "Link", @@ -577,6 +564,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "rejected_serial_no", "fieldtype": "Small Text", "label": "Rejected Serial No", @@ -814,11 +802,28 @@ "fieldtype": "Data", "label": "Manufacturer Part Number", "read_only": 1 + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_location", + "fieldtype": "Link", + "label": "Asset Location", + "options": "Location" + }, + { + "depends_on": "is_fixed_asset", + "fetch_from": "item_code.asset_category", + "fieldname": "asset_category", + "fieldtype": "Link", + "in_preview": 1, + "label": "Asset Category", + "options": "Asset Category", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-09-17 22:33:01.109004", + "modified": "2019-10-14 16:03:25.499557", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 87c6718d90de2648918e9ef28a7674e67ce9e1c3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 18 Nov 2019 12:34:30 +0530 Subject: [PATCH 118/131] fix: Book valuation expense in specified account rather than expense included in valuation account (#19590) * fix: Book valuation expense in specified accout rather than expense included in valuation account * fix: Remove undefined variable * fix: Test cases * fix: Test Case --- .../purchase_invoice/purchase_invoice.py | 60 ++++++++++--------- .../purchase_invoice/test_purchase_invoice.py | 29 +++++++-- .../test_landed_cost_voucher.py | 5 +- .../purchase_receipt/purchase_receipt.py | 47 ++++++++------- .../purchase_receipt/test_purchase_receipt.py | 5 +- 5 files changed, 88 insertions(+), 58 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4fbf9a1009..5c53d26ad1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -714,14 +714,14 @@ class PurchaseInvoice(BuyingController): if account_currency==self.company_currency \ else tax.tax_amount_after_discount_amount, "cost_center": tax.cost_center - }, account_currency) + }, account_currency, item=tax) ) # accumulate valuation tax if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if self.auto_accounting_for_stock and not tax.cost_center: frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) - valuation_tax.setdefault(tax.cost_center, 0) - valuation_tax[tax.cost_center] += \ + valuation_tax.setdefault(tax.name, 0) + valuation_tax[tax.name] += \ (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax: @@ -731,36 +731,38 @@ class PurchaseInvoice(BuyingController): total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = self.negative_expense_to_be_booked i = 1 - for cost_center, amount in iteritems(valuation_tax): - if i == len(valuation_tax): - applicable_amount = amount_including_divisional_loss - else: - applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount) - amount_including_divisional_loss -= applicable_amount + for tax in self.get("taxes"): + if valuation_tax.get(tax.name): + if i == len(valuation_tax): + applicable_amount = amount_including_divisional_loss + else: + applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) + amount_including_divisional_loss -= applicable_amount - gl_entries.append( - self.get_gl_dict({ - "account": self.expenses_included_in_valuation, - "cost_center": cost_center, - "against": self.supplier, - "credit": applicable_amount, - "remarks": self.remarks or "Accounting Entry for Stock" - }) - ) + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "cost_center": tax.cost_center, + "against": self.supplier, + "credit": applicable_amount, + "remarks": self.remarks or _("Accounting Entry for Stock"), + }, item=tax) + ) - i += 1 + i += 1 if self.auto_accounting_for_stock and self.update_stock and valuation_tax: - for cost_center, amount in iteritems(valuation_tax): - gl_entries.append( - self.get_gl_dict({ - "account": self.expenses_included_in_valuation, - "cost_center": cost_center, - "against": self.supplier, - "credit": amount, - "remarks": self.remarks or "Accounting Entry for Stock" - }) - ) + for tax in self.get("taxes"): + if valuation_tax.get(tax.name): + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "cost_center": tax.cost_center, + "against": self.supplier, + "credit": valuation_tax[tax.name], + "remarks": self.remarks or "Accounting Entry for Stock" + }, item=tax) + ) def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b2ad4f4d51..85b1166790 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -204,19 +204,40 @@ class TestPurchaseInvoice(unittest.TestCase): pi.insert() pi.submit() - self.check_gle_for_pi(pi.name) + self.check_gle_for_pi_against_pr(pi.name) def check_gle_for_pi(self, pi): - gl_entries = frappe.db.sql("""select account, debit, credit + gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", pi, as_dict=1) + group by account""", pi, as_dict=1) + self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ ["Creditors - TCP1", 0, 720], ["Stock Received But Not Billed - TCP1", 500.0, 0], - ["_Test Account Shipping Charges - TCP1", 100.0, 0], + ["_Test Account Shipping Charges - TCP1", 100.0, 0.0], + ["_Test Account VAT - TCP1", 120.0, 0] + ]) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) + + def check_gle_for_pi_against_pr(self, pi): + gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + group by account""", pi, as_dict=1) + + self.assertTrue(gl_entries) + + expected_values = dict((d[0], d) for d in [ + ["Creditors - TCP1", 0, 720], + ["Stock Received But Not Billed - TCP1", 750.0, 0], + ["_Test Account Shipping Charges - TCP1", 100.0, 100.0], ["_Test Account VAT - TCP1", 120.0, 0], + ["_Test Account Customs Duty - TCP1", 0, 150] ]) for i, gle in enumerate(gl_entries): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index fe5d3ed6df..988cf52ed0 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -54,9 +54,10 @@ class TestLandedCostVoucher(unittest.TestCase): expected_values = { stock_in_hand_account: [800.0, 0.0], "Stock Received But Not Billed - TCP1": [0.0, 500.0], - "Expenses Included In Valuation - TCP1": [0.0, 300.0] + "Expenses Included In Valuation - TCP1": [0.0, 50.0], + "_Test Account Customs Duty - TCP1": [0.0, 150], + "_Test Account Shipping Charges - TCP1": [0.0, 100.00] } - else: expected_values = { stock_in_hand_account: [400.0, 0.0], diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 56fd47e1bc..0cb21d73f9 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -288,8 +288,8 @@ class PurchaseReceipt(BuyingController): if tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if not tax.cost_center: frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) - valuation_tax.setdefault(tax.cost_center, 0) - valuation_tax[tax.cost_center] += \ + valuation_tax.setdefault(tax.name, 0) + valuation_tax[tax.name] += \ (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) if negative_expense_to_be_booked and valuation_tax: @@ -297,37 +297,42 @@ class PurchaseReceipt(BuyingController): # If expenses_included_in_valuation account has been credited in against PI # and charges added via Landed Cost Voucher, # post valuation related charges on "Stock Received But Not Billed" + # introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account negative_expense_booked_in_pi = frappe.db.sql("""select name from `tabPurchase Invoice Item` pi where docstatus = 1 and purchase_receipt=%s and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=pi.parent and account=%s)""", (self.name, expenses_included_in_valuation)) - if negative_expense_booked_in_pi: - expenses_included_in_valuation = stock_rbnb - against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked i = 1 - for cost_center, amount in iteritems(valuation_tax): - if i == len(valuation_tax): - applicable_amount = amount_including_divisional_loss - else: - applicable_amount = negative_expense_to_be_booked * (amount / total_valuation_amount) - amount_including_divisional_loss -= applicable_amount + for tax in self.get("taxes"): + if valuation_tax.get(tax.name): - gl_entries.append( - self.get_gl_dict({ - "account": expenses_included_in_valuation, - "cost_center": cost_center, - "credit": applicable_amount, - "remarks": self.remarks or _("Accounting Entry for Stock"), - "against": against_account - }) - ) + if negative_expense_booked_in_pi: + account = stock_rbnb + else: + account = tax.account_head - i += 1 + if i == len(valuation_tax): + applicable_amount = amount_including_divisional_loss + else: + applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) + amount_including_divisional_loss -= applicable_amount + + gl_entries.append( + self.get_gl_dict({ + "account": account, + "cost_center": tax.cost_center, + "credit": applicable_amount, + "remarks": self.remarks or _("Accounting Entry for Stock"), + "against": against_account + }, item=tax) + ) + + i += 1 if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 2afb69353f..c80b9bd04b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -66,14 +66,15 @@ class TestPurchaseReceipt(unittest.TestCase): expected_values = { stock_in_hand_account: [750.0, 0.0], "Stock Received But Not Billed - TCP1": [0.0, 500.0], - "Expenses Included In Valuation - TCP1": [0.0, 250.0] + "_Test Account Shipping Charges - TCP1": [0.0, 100.0], + "_Test Account Customs Duty - TCP1": [0.0, 150.0] } else: expected_values = { stock_in_hand_account: [375.0, 0.0], fixed_asset_account: [375.0, 0.0], "Stock Received But Not Billed - TCP1": [0.0, 500.0], - "Expenses Included In Valuation - TCP1": [0.0, 250.0] + "_Test Account Shipping Charges - TCP1": [0.0, 250.0] } for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) From a99897841536fb34508dc549a4a917ab22ee6b6a Mon Sep 17 00:00:00 2001 From: RJPvT <48353029+RJPvT@users.noreply.github.com> Date: Tue, 19 Nov 2019 10:24:38 +0100 Subject: [PATCH 119/131] fix: pending on review date (#19609) * fix: On Specific case if no item code in name * fix: pending on review date --- erpnext/projects/doctype/task/task.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 54fce8d6db..7083d694f8 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -7,7 +7,7 @@ import json import frappe from frappe import _, throw -from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate +from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today from frappe.utils.nestedset import NestedSet from frappe.desk.form.assign_to import close_all_assignments, clear from frappe.utils import date_diff @@ -212,8 +212,11 @@ def set_multiple_status(names, status): task.save() def set_tasks_as_overdue(): - tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Completed']]}) + tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]}) for task in tasks: + if frappe.db.get_value("Task", task.name, "status") in 'Pending Review': + if getdate(frappe.db.get_value("Task", task.name, "review_date")) < getdate(today()): + continue frappe.get_doc("Task", task.name).update_status() @frappe.whitelist() From 3f854fce2eb6e9c15d6e5f84ad2d5d14b8e7882c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 19 Nov 2019 15:07:30 +0530 Subject: [PATCH 120/131] feat: fetch leave approver from both employee and department approvers (#19613) * fix: fetch leave approvers from both department and employee master * fix: creaate a set of approvers --- .../doctype/department_approver/department_approver.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index d6b66da081..df0f75a18c 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -20,10 +20,6 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): department_details = {} department_list = [] employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) - if employee.leave_approver: - approver = frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']) - approvers.append(approver) - return approvers employee_department = filters.get("department") or employee.department if employee_department: @@ -34,6 +30,9 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and disabled=0 order by lft desc""", (department_details.lft, department_details.rgt), as_list=True) + if filters.get("doctype") == "Leave Application" and employee.leave_approver: + approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name'])) + if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" else: @@ -47,4 +46,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and approver.parentfield = %s and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) - return approvers + return set(tuple(approver) for approver in approvers) From 9db9edca2c785e3a4ed784e1a3f3c38e102b662b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 19 Nov 2019 18:44:32 +0530 Subject: [PATCH 121/131] fix(expense claim): fetch outstanding documents based on party account type --- .../purchase_invoice/purchase_invoice.py | 16 ++++++------ erpnext/accounts/utils.py | 12 ++++++--- .../hr/doctype/expense_claim/expense_claim.py | 26 ------------------- 3 files changed, 16 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5c53d26ad1..ba7ad37c8d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -357,7 +357,7 @@ class PurchaseInvoice(BuyingController): return if not gl_entries: gl_entries = self.get_gl_entries() - + if gl_entries: update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" @@ -504,7 +504,7 @@ class PurchaseInvoice(BuyingController): asset_category)): expense_account = (item.expense_account if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) - + if not item.is_fixed_asset: amount = flt(item.base_net_amount, item.precision("base_net_amount")) else: @@ -517,7 +517,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "project": item.project }, account_currency, item=item)) - + # If asset is bought through this document and not linked to PR if self.update_stock and item.landed_cost_voucher_amount: expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") @@ -539,9 +539,9 @@ class PurchaseInvoice(BuyingController): "debit": flt(item.landed_cost_voucher_amount), "project": item.project }, item=item)) - + # update gross amount of asset bought through this document - assets = frappe.db.get_all('Asset', + assets = frappe.db.get_all('Asset', filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } ) for asset in assets: @@ -633,7 +633,7 @@ class PurchaseInvoice(BuyingController): if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) }, item=item)) - + # When update stock is checked # Assets are bought through this document then it will be linked to this document if self.update_stock: @@ -655,9 +655,9 @@ class PurchaseInvoice(BuyingController): "debit": flt(item.landed_cost_voucher_amount), "project": item.project }, item=item)) - + # update gross amount of assets bought through this document - assets = frappe.db.get_all('Asset', + assets = frappe.db.get_all('Asset', filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } ) for asset in assets: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 382a89b310..94697be02f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -630,7 +630,7 @@ def get_held_invoices(party_type, party): 'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()', as_dict=1 ) - held_invoices = [d['name'] for d in held_invoices] + held_invoices = set([d['name'] for d in held_invoices]) return held_invoices @@ -639,14 +639,19 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters outstanding_invoices = [] precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2 - if erpnext.get_party_account_type(party_type) == 'Receivable': + if account: + root_type = frappe.get_cached_value("Account", account, "root_type") + party_account_type = "Receivable" if root_type == "Asset" else "Payable" + else: + party_account_type = erpnext.get_party_account_type(party_type) + + if party_account_type == 'Receivable': dr_or_cr = "debit_in_account_currency - credit_in_account_currency" payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency" else: dr_or_cr = "credit_in_account_currency - debit_in_account_currency" payment_dr_or_cr = "debit_in_account_currency - credit_in_account_currency" - invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice' held_invoices = get_held_invoices(party_type, party) invoice_list = frappe.db.sql(""" @@ -665,7 +670,6 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters group by voucher_type, voucher_no order by posting_date, name""".format( dr_or_cr=dr_or_cr, - invoice = invoice, condition=condition or "" ), { "party_type": party_type, diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index f0036277c8..59391505fa 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -140,32 +140,6 @@ class ExpenseClaim(AccountsController): "against": ",".join([d.default_account for d in self.expenses]), "party_type": "Employee", "party": self.employee, - "against_voucher_type": self.doctype, - "against_voucher": self.name - }) - ) - - gl_entry.append( - self.get_gl_dict({ - "account": data.advance_account, - "debit": data.allocated_amount, - "debit_in_account_currency": data.allocated_amount, - "against": self.payable_account, - "party_type": "Employee", - "party": self.employee, - "against_voucher_type": self.doctype, - "against_voucher": self.name - }) - ) - - gl_entry.append( - self.get_gl_dict({ - "account": self.payable_account, - "credit": data.allocated_amount, - "credit_in_account_currency": data.allocated_amount, - "against": data.advance_account, - "party_type": "Employee", - "party": self.employee, "against_voucher_type": "Employee Advance", "against_voucher": data.employee_advance }) From eed30c6d8c3a4855e6d580b66bcf85a922647a9f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 19 Nov 2019 19:05:23 +0530 Subject: [PATCH 122/131] fix: not able to select item in sales order --- erpnext/controllers/queries.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 3830ca0361..7b4a4c92ad 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -159,8 +159,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if "description" in searchfields: searchfields.remove("description") - columns = [field for field in searchfields if not field in ["name", "item_group", "description"]] - columns = ", ".join(columns) + columns = '' + extra_searchfields = [field for field in searchfields + if not field in ["name", "item_group", "description"]] + + if extra_searchfields: + columns = ", " + ", ".join(extra_searchfields) searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"] if not field in searchfields] @@ -176,7 +180,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, tabItem.item_group, if(length(tabItem.description) > 40, \ - concat(substr(tabItem.description, 1, 40), "..."), description) as description, + concat(substr(tabItem.description, 1, 40), "..."), description) as description {columns} from tabItem where tabItem.docstatus < 2 From a8318480744dac43e724a63cc0cd356198e872b1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 19 Nov 2019 19:21:27 +0530 Subject: [PATCH 123/131] fix: code cleanup --- erpnext/controllers/sales_and_purchase_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 859529204b..81fdbbefc3 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -72,7 +72,7 @@ def validate_returned_items(doc): items_returned = False for d in doc.get("items"): - if d.item_code and (flt(d.qty) < 0 or d.get('received_qty') < 0): + if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0): if d.item_code not in valid_items: frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") .format(d.idx, d.item_code, doc.doctype, doc.return_against)) From 150c44b350f85bf7d566a07b3eb99dda205403ff Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 22 Nov 2019 11:08:35 +0530 Subject: [PATCH 124/131] fix: asset movement ux fixes (#19641) --- erpnext/assets/doctype/asset/asset.js | 117 +++++------------- erpnext/assets/doctype/asset/asset.py | 4 +- erpnext/assets/doctype/asset/asset_list.js | 1 + .../doctype/asset_movement/asset_movement.js | 2 +- .../asset_movement/asset_movement.json | 6 +- 5 files changed, 38 insertions(+), 92 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index f0889bfa1b..6b3f2c777c 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -42,6 +42,24 @@ frappe.ui.form.on('Asset', { }, setup: function(frm) { + frm.make_methods = { + 'Asset Movement': () => { + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.make_asset_movement", + freeze: true, + args:{ + "assets": [{ name: cur_frm.doc.name }] + }, + callback: function (r) { + if (r.message) { + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }, + } + frm.set_query("purchase_receipt", (doc) => { return { query: "erpnext.controllers.queries.get_purchase_receipts", @@ -487,92 +505,19 @@ erpnext.asset.restore_asset = function(frm) { }) }; -erpnext.asset.transfer_asset = function(frm) { - var dialog = new frappe.ui.Dialog({ - title: __("Transfer Asset"), - fields: [ - { - "label": __("Target Location"), - "fieldname": "target_location", - "fieldtype": "Link", - "options": "Location", - "get_query": function () { - return { - filters: [ - ["Location", "is_group", "=", 0] - ] - } - }, - "reqd": 1 - }, - { - "label": __("Select Serial No"), - "fieldname": "serial_nos", - "fieldtype": "Link", - "options": "Serial No", - "get_query": function () { - return { - filters: { - 'asset': frm.doc.name - } - } - }, - "onchange": function() { - let val = this.get_value(); - if (val) { - let serial_nos = dialog.get_value("serial_no") || val; - if (serial_nos) { - serial_nos = serial_nos.split('\n'); - serial_nos.push(val); - - const unique_sn = serial_nos.filter(function(elem, index, self) { - return index === self.indexOf(elem); - }); - - dialog.set_value("serial_no", unique_sn.join('\n')); - dialog.set_value("serial_nos", ""); - } - } - } - }, - { - "label": __("Serial No"), - "fieldname": "serial_no", - "read_only": 1, - "fieldtype": "Small Text" - }, - { - "label": __("Date"), - "fieldname": "transfer_date", - "fieldtype": "Datetime", - "reqd": 1, - "default": frappe.datetime.now_datetime() +erpnext.asset.transfer_asset = function() { + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.make_asset_movement", + freeze: true, + args:{ + "assets": [{ name: cur_frm.doc.name }], + "purpose": "Transfer" + }, + callback: function (r) { + if (r.message) { + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); } - ] + } }); - - 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_location": frm.doc.location, - "target_location": args.target_location, - "serial_no": args.serial_no, - "company": frm.doc.company - } - }, - freeze: true, - callback: function(r) { - cur_frm.reload_doc(); - } - }) - }); - dialog.show(); }; diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9415eedc5c..8b6bc40cf0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -646,7 +646,7 @@ def make_journal_entry(asset_name): return je @frappe.whitelist() -def make_asset_movement(assets): +def make_asset_movement(assets, purpose=None): import json from six import string_types @@ -657,7 +657,7 @@ def make_asset_movement(assets): frappe.throw(_('Atleast one asset has to be selected.')) asset_movement = frappe.new_doc("Asset Movement") - asset_movement.quantity = len(assets) + asset_movement.purpose = purpose prev_reference_docname = '' for asset in assets: diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js index 46cde6ee81..02f39e0e7f 100644 --- a/erpnext/assets/doctype/asset/asset_list.js +++ b/erpnext/assets/doctype/asset/asset_list.js @@ -37,6 +37,7 @@ frappe.listview_settings['Asset'] = { const assets = me.get_checked_items(); frappe.call({ method: "erpnext.assets.doctype.asset.asset.make_asset_movement", + freeze: true, args:{ "assets": assets }, diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index a71212ea47..89977e2952 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -132,7 +132,7 @@ frappe.ui.form.on('Asset Movement Item', { if(asset_doc.location) frappe.model.set_value(cdt, cdn, 'source_location', asset_doc.location); if(asset_doc.custodian) frappe.model.set_value(cdt, cdn, 'from_employee', asset_doc.custodian); }).catch((err) => { - console.log(err); + console.log(err); // eslint-disable-line }); } } diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index 19af81d65b..e62d684411 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -54,7 +54,7 @@ { "fieldname": "reference_doctype", "fieldtype": "Link", - "label": "Reference DocType", + "label": "Reference Document", "no_copy": 1, "options": "DocType", "reqd": 1 @@ -62,7 +62,7 @@ { "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "label": "Reference Name", + "label": "Reference Document Name", "no_copy": 1, "options": "reference_doctype", "reqd": 1 @@ -93,7 +93,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-13 15:37:48.870147", + "modified": "2019-11-21 14:35:51.880332", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", From 7af153da50fbf6ab87d407f4ae9b2077873efc1c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 22 Nov 2019 11:35:14 +0530 Subject: [PATCH 125/131] fix: Multiple fixes related to landed cost accounting (#19656) --- .../purchase_invoice/purchase_invoice.py | 10 ++++++--- .../purchase_invoice/test_purchase_invoice.py | 22 +------------------ .../purchase_invoice_item.json | 4 ++-- .../doctype/sales_invoice/sales_invoice.py | 2 +- .../landed_cost_voucher.json | 6 ++--- 5 files changed, 14 insertions(+), 30 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index ba7ad37c8d..c0023560ff 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -452,6 +452,10 @@ class PurchaseInvoice(BuyingController): fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}): voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference) + valuation_tax_accounts = [d.account_head for d in self.get("taxes") + if d.category in ('Valuation', 'Total and Valuation') + and flt(d.base_tax_amount_after_discount_amount)] + for item in self.get("items"): if flt(item.base_net_amount): account_currency = get_account_currency(item.expense_account) @@ -551,10 +555,10 @@ class PurchaseInvoice(BuyingController): if self.auto_accounting_for_stock and self.is_opening == "No" and \ item.item_code in stock_items and item.item_tax_amount: # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt - if item.purchase_receipt: + if item.purchase_receipt and valuation_tax_accounts: negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""", - (item.purchase_receipt, self.expenses_included_in_valuation)) + where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""", + (item.purchase_receipt, valuation_tax_accounts)) if not negative_expense_booked_in_pr: gl_entries.append( diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 85b1166790..e41ad42846 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -204,7 +204,7 @@ class TestPurchaseInvoice(unittest.TestCase): pi.insert() pi.submit() - self.check_gle_for_pi_against_pr(pi.name) + self.check_gle_for_pi(pi.name) def check_gle_for_pi(self, pi): gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit @@ -225,26 +225,6 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_values[gle.account][1], gle.debit) self.assertEqual(expected_values[gle.account][2], gle.credit) - def check_gle_for_pi_against_pr(self, pi): - gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - group by account""", pi, as_dict=1) - - self.assertTrue(gl_entries) - - expected_values = dict((d[0], d) for d in [ - ["Creditors - TCP1", 0, 720], - ["Stock Received But Not Billed - TCP1", 750.0, 0], - ["_Test Account Shipping Charges - TCP1", 100.0, 100.0], - ["_Test Account VAT - TCP1", 120.0, 0], - ["_Test Account Customs Duty - TCP1", 0, 150] - ]) - - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][0], gle.account) - self.assertEqual(expected_values[gle.account][1], gle.debit) - self.assertEqual(expected_values[gle.account][2], gle.credit) - def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index dc3a1be0c7..27d8233a44 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -117,6 +117,7 @@ }, { "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, @@ -192,7 +193,6 @@ "fieldtype": "Column Break" }, { - "fetch_from": "item_code.stock_uom", "fieldname": "uom", "fieldtype": "Link", "label": "UOM", @@ -766,7 +766,7 @@ ], "idx": 1, "istable": 1, - "modified": "2019-11-03 13:43:23.782877", + "modified": "2019-11-21 16:27:52.043744", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3d96d487a8..70a80ca184 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -135,7 +135,7 @@ class SalesInvoice(SellingController): if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: validate_loyalty_points(self, self.loyalty_points) - + def validate_fixed_asset(self): for d in self.get("items"): if d.is_fixed_asset and d.meta.get_field("asset") and d.asset: diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json index 46fdc8fc10..01492807de 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -8,11 +8,11 @@ "naming_series", "company", "purchase_receipts", - "sec_break1", - "taxes", "purchase_receipt_items", "get_items_from_purchase_receipts", "items", + "sec_break1", + "taxes", "section_break_9", "total_taxes_and_charges", "col_break1", @@ -123,7 +123,7 @@ ], "icon": "icon-usd", "is_submittable": 1, - "modified": "2019-10-09 13:39:36.082777", + "modified": "2019-11-21 15:34:10.846093", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Voucher", From fa4299931455bccdf096329eb1f95a8af77e83d3 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Tue, 19 Nov 2019 14:19:52 +0530 Subject: [PATCH 126/131] fix: last purchase rate greater than selling price --- .../buying/doctype/purchase_order/purchase_order.py | 2 +- erpnext/buying/utils.py | 4 ++-- erpnext/stock/doctype/item/item.py | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 845ff747d6..f62df20ae1 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -313,7 +313,7 @@ def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor= last_purchase_details = get_last_purchase_details(item_code, name) if last_purchase_details: - last_purchase_rate = (last_purchase_details['base_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate + last_purchase_rate = (last_purchase_details['base_net_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate return last_purchase_rate else: item_last_purchase_rate = frappe.get_cached_value("Item", item_code, "last_purchase_rate") diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index 8c0a1e56f7..b5598f8d0b 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -24,12 +24,12 @@ def update_last_purchase_rate(doc, is_submit): last_purchase_rate = None if last_purchase_details and \ (last_purchase_details.purchase_date > this_purchase_date): - last_purchase_rate = last_purchase_details['base_rate'] + last_purchase_rate = last_purchase_details['base_net_rate'] elif is_submit == 1: # even if this transaction is the latest one, it should be submitted # for it to be considered for latest purchase rate if flt(d.conversion_factor): - last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) + last_purchase_rate = flt(d.base_net_rate) / flt(d.conversion_factor) # Check if item code is present # Conversion factor should not be mandatory for non itemized items elif d.item_code: diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 164c659fe8..7495dffec2 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -645,7 +645,7 @@ class Item(WebsiteGenerator): json.dumps(item_wise_tax_detail), update_modified=False) def set_last_purchase_rate(self, new_name): - last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0) + last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) def recalculate_bin_qty(self, new_name): @@ -942,7 +942,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): last_purchase_order = frappe.db.sql("""\ select po.name, po.transaction_date, po.conversion_rate, po_item.conversion_factor, po_item.base_price_list_rate, - po_item.discount_percentage, po_item.base_rate + po_item.discount_percentage, po_item.base_rate, po_item.base_net_rate from `tabPurchase Order` po, `tabPurchase Order Item` po_item where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and po.name = po_item.parent @@ -953,7 +953,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): last_purchase_receipt = frappe.db.sql("""\ select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage, - pr_item.base_rate + pr_item.base_rate, pr_item.base_net_rate from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and pr.name = pr_item.parent @@ -984,6 +984,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): out = frappe._dict({ "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, "base_rate": flt(last_purchase.base_rate) / conversion_factor, + "base_net_rate": flt(last_purchase.net_rate) / conversion_factor, "discount_percentage": flt(last_purchase.discount_percentage), "purchase_date": purchase_date }) @@ -992,7 +993,8 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): out.update({ "price_list_rate": out.base_price_list_rate / conversion_rate, "rate": out.base_rate / conversion_rate, - "base_rate": out.base_rate + "base_rate": out.base_rate, + "base_net_rate": out.base_net_rate }) return out From f37a46edea1ef8ce9e0041241c9dc95b6130e124 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 22 Nov 2019 16:32:50 +0530 Subject: [PATCH 127/131] Fixed Asset Refactor Review fixes (#19665) * fix: fixed asset item creation ux fixes * fix: auto creation of asset ux fixes * fix: [LCV] incorrect condition when checking assets linked with PR * fix: bulk update assets * refac: remove company level cwip enabling * cwip can be enabled only on category level * fix: #19649 --- .../purchase_invoice/purchase_invoice.py | 7 +- erpnext/accounts/general_ledger.py | 6 +- erpnext/assets/doctype/asset/asset.py | 18 +- erpnext/assets/doctype/asset/test_asset.py | 1004 ++++++++--------- .../doctype/asset_category/asset_category.py | 10 - .../asset_value_adjustment.json | 5 +- .../asset_value_adjustment.py | 9 +- erpnext/controllers/buying_controller.py | 13 +- .../set_cwip_and_delete_asset_settings.py | 18 +- erpnext/setup/doctype/company/company.json | 9 +- erpnext/stock/doctype/item/item.js | 9 +- .../landed_cost_voucher.py | 7 +- .../purchase_receipt/purchase_receipt.py | 14 +- 13 files changed, 562 insertions(+), 567 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c0023560ff..3bb3df8dbd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -237,7 +237,7 @@ class PurchaseInvoice(BuyingController): item.expense_account = warehouse_account[item.warehouse]["account"] else: item.expense_account = stock_not_billed_account - elif item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, asset_category): + elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code, company = self.company) elif item.is_fixed_asset and item.pr_detail: @@ -408,7 +408,7 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): if item.item_code and item.is_fixed_asset: asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - if is_cwip_accounting_enabled(self.company, asset_category): + if is_cwip_accounting_enabled(asset_category): return 1 return 0 @@ -504,8 +504,7 @@ class PurchaseInvoice(BuyingController): "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) - elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, - asset_category)): + elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)): expense_account = (item.expense_account if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 38f283c8d4..e9703dd790 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -175,11 +175,7 @@ def validate_account_for_perpetual_inventory(gl_map): StockValueAndAccountBalanceOutOfSync, title=_('Account Balance Out Of Sync')) def validate_cwip_accounts(gl_map): - cwip_enabled = cint(frappe.get_cached_value("Company", - gl_map[0].company, "enable_cwip_accounting")) - - if not cwip_enabled: - cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) + cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8b6bc40cf0..546f374094 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -31,8 +31,7 @@ class Asset(AccountsController): self.validate_in_use_date() self.set_status() self.make_asset_movement() - if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.company, - self.asset_category): + if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category): self.make_gl_entries() def before_cancel(self): @@ -99,7 +98,7 @@ class Asset(AccountsController): if not flt(self.gross_purchase_amount): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) - if is_cwip_accounting_enabled(self.company, self.asset_category): + if is_cwip_accounting_enabled(self.asset_category): if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}"). format(self.item_code)) @@ -295,7 +294,9 @@ class Asset(AccountsController): .format(row.idx)) if not row.depreciation_start_date: - frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx)) + if not self.available_for_use_date: + frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx)) + row.depreciation_start_date = self.available_for_use_date if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 @@ -514,7 +515,7 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): - if not is_cwip_accounting_enabled(self.company, self.asset_category): + if not is_cwip_accounting_enabled(self.asset_category): return assets = frappe.db.sql_list(""" select name from `tabAsset` @@ -683,12 +684,7 @@ def make_asset_movement(assets, purpose=None): if asset_movement.get('assets'): return asset_movement.as_dict() -def is_cwip_accounting_enabled(company, asset_category=None): - enable_cwip_in_company = cint(frappe.db.get_value("Company", company, "enable_cwip_accounting")) - - if enable_cwip_in_company or not asset_category: - return enable_cwip_in_company - +def is_cwip_accounting_enabled(asset_category): return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 53fd6d394d..a56440de3d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -69,508 +69,508 @@ class TestAsset(unittest.TestCase): self.assertFalse(frappe.db.get_value("GL Entry", {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) - # def test_is_fixed_asset_set(self): - # asset = create_asset(is_existing_asset = 1) - # doc = frappe.new_doc('Purchase Invoice') - # doc.supplier = '_Test Supplier' - # doc.append('items', { - # 'item_code': 'Macbook Pro', - # 'qty': 1, - # 'asset': asset.name - # }) - - # doc.set_missing_values() - # self.assertEquals(doc.items[0].is_fixed_asset, 1) - - - # def test_schedule_for_straight_line_method(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2030-01-01' - # asset.purchase_date = '2030-01-01' - - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": "2030-12-31" - # }) - # asset.save() - - # self.assertEqual(asset.status, "Draft") - # expected_schedules = [ - # ["2030-12-31", 30000.00, 30000.00], - # ["2031-12-31", 30000.00, 60000.00], - # ["2032-12-31", 30000.00, 90000.00] - # ] - - # 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): - # create_asset(is_existing_asset=1) - # asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - # asset.calculate_depreciation = 1 - # asset.number_of_depreciations_booked = 1 - # asset.opening_accumulated_depreciation = 40000 - # asset.available_for_use_date = "2030-06-06" - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": "2030-12-31" - # }) - # asset.insert() - # self.assertEqual(asset.status, "Draft") - # asset.save() - # expected_schedules = [ - # ["2030-12-31", 14246.58, 54246.58], - # ["2031-12-31", 25000.00, 79246.58], - # ["2032-06-06", 10753.42, 90000.00] - # ] - # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] - # for d in asset.get("schedules")] - - # self.assertEqual(schedules, expected_schedules) - - # def test_schedule_for_double_declining_method(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2030-01-01' - # asset.purchase_date = '2030-01-01' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Double Declining Balance", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": '2030-12-31' - # }) - # asset.insert() - # self.assertEqual(asset.status, "Draft") - # asset.save() - - # expected_schedules = [ - # ['2030-12-31', 66667.00, 66667.00], - # ['2031-12-31', 22222.11, 88889.11], - # ['2032-12-31', 1110.89, 90000.0] - # ] - - # 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): - # create_asset(is_existing_asset = 1) - # asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - # asset.calculate_depreciation = 1 - # asset.is_existing_asset = 1 - # asset.number_of_depreciations_booked = 1 - # asset.opening_accumulated_depreciation = 50000 - # asset.available_for_use_date = '2030-01-01' - # asset.purchase_date = '2029-11-30' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Double Declining Balance", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": "2030-12-31" - # }) - # asset.insert() - # self.assertEqual(asset.status, "Draft") - - # expected_schedules = [ - # ["2030-12-31", 33333.50, 83333.50], - # ["2031-12-31", 6666.50, 90000.0] - # ] - - # 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_prorated_straight_line_method(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.purchase_date = '2030-01-30' - # asset.is_existing_asset = 0 - # asset.available_for_use_date = "2030-01-30" - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": "2030-12-31" - # }) - - # asset.insert() - # asset.save() - - # expected_schedules = [ - # ["2030-12-31", 27534.25, 27534.25], - # ["2031-12-31", 30000.0, 57534.25], - # ["2032-12-31", 30000.0, 87534.25], - # ["2033-01-30", 2465.75, 90000.0] - # ] - - # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - # for d in asset.get("schedules")] - - # self.assertEqual(schedules, expected_schedules) - - # def test_depreciation(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.purchase_date = '2020-01-30' - # asset.available_for_use_date = "2020-01-30" - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 10, - # "depreciation_start_date": "2020-12-31" - # }) - # asset.insert() - # 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() - - # # 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"), 0) - - # def test_depreciation_entry_for_wdv_without_pro_rata(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=8000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2030-01-01' - # asset.purchase_date = '2030-01-01' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 1000, - # "depreciation_method": "Written Down Value", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": "2030-12-31" - # }) - # asset.save(ignore_permissions=True) - - # self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - # expected_schedules = [ - # ["2030-12-31", 4000.00, 4000.00], - # ["2031-12-31", 2000.00, 6000.00], - # ["2032-12-31", 1000.00, 7000.0], - # ] - - # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - # for d in asset.get("schedules")] - - # self.assertEqual(schedules, expected_schedules) - - # def test_pro_rata_depreciation_entry_for_wdv(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=8000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2030-06-06' - # asset.purchase_date = '2030-01-01' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 1000, - # "depreciation_method": "Written Down Value", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 12, - # "depreciation_start_date": "2030-12-31" - # }) - # asset.save(ignore_permissions=True) - - # self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - # expected_schedules = [ - # ["2030-12-31", 2279.45, 2279.45], - # ["2031-12-31", 2860.28, 5139.73], - # ["2032-12-31", 1430.14, 6569.87], - # ["2033-06-06", 430.13, 7000.0], - # ] - - # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - # for d in asset.get("schedules")] - - # self.assertEqual(schedules, expected_schedules) - - # def test_depreciation_entry_cancellation(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2020-06-06' - # asset.purchase_date = '2020-06-06' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 10, - # "depreciation_start_date": "2020-12-31" - # }) - # asset.insert() - # 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): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = nowdate() - # asset.purchase_date = nowdate() - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 10, - # "depreciation_start_date": nowdate() - # }) - # asset.insert() - # asset.submit() - - # post_depreciation_entries(date=add_months(nowdate(), 10)) - - # scrap_asset(asset.name) - - # 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(asset.name) - - # asset.load_from_db() - # self.assertFalse(asset.journal_entry_for_scrap) - # self.assertEqual(asset.status, "Partially Depreciated") - - # def test_asset_sale(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2020-06-06' - # asset.purchase_date = '2020-06-06' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 10, - # "depreciation_start_date": "2020-12-31" - # }) - # asset.insert() - # asset.submit() - # post_depreciation_entries(date="2021-01-01") - - # si = make_sales_invoice(asset=asset.name, 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", asset.name, "status"), "Sold") - - # expected_gle = ( - # ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), - # ("_Test Fixed Asset - _TC", 0.0, 100000.0), - # ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 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", asset.name, "status"), "Partially Depreciated") - - # def test_asset_expected_value_after_useful_life(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location") - - # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - # asset = frappe.get_doc('Asset', asset_name) - # asset.calculate_depreciation = 1 - # asset.available_for_use_date = '2020-06-06' - # asset.purchase_date = '2020-06-06' - # asset.append("finance_books", { - # "expected_value_after_useful_life": 10000, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 10, - # "depreciation_start_date": "2020-06-06" - # }) - # asset.insert() - # 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.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) - - # def test_cwip_accounting(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=5000, do_not_submit=True, location="Test Location") - - # pr.set('taxes', [{ - # 'category': 'Total', - # 'add_deduct_tax': 'Add', - # 'charge_type': 'On Net Total', - # 'account_head': '_Test Account Service Tax - _TC', - # 'description': '_Test Account Service Tax', - # 'cost_center': 'Main - _TC', - # 'rate': 5.0 - # }, { - # 'category': 'Valuation and Total', - # 'add_deduct_tax': 'Add', - # 'charge_type': 'On Net Total', - # 'account_head': '_Test Account Shipping Charges - _TC', - # 'description': '_Test Account Shipping Charges', - # 'cost_center': 'Main - _TC', - # 'rate': 5.0 - # }]) - - # pr.submit() - - # expected_gle = ( - # ("Asset Received But Not Billed - _TC", 0.0, 5250.0), - # ("CWIP Account - _TC", 5250.0, 0.0) - # ) - - # pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - # where voucher_type='Purchase Receipt' and voucher_no = %s - # order by account""", pr.name) - - # self.assertEqual(pr_gle, expected_gle) - - # pi = make_invoice(pr.name) - # pi.submit() - - # expected_gle = ( - # ("_Test Account Service Tax - _TC", 250.0, 0.0), - # ("_Test Account Shipping Charges - _TC", 250.0, 0.0), - # ("Asset Received But Not Billed - _TC", 5250.0, 0.0), - # ("Creditors - _TC", 0.0, 5500.0), - # ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), - # ) - - # pi_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(pi_gle, expected_gle) - - # asset = frappe.db.get_value('Asset', - # {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') - - # asset_doc = frappe.get_doc('Asset', asset) - - # month_end_date = get_last_day(nowdate()) - # asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - # self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) - - # asset_doc.append("finance_books", { - # "expected_value_after_useful_life": 200, - # "depreciation_method": "Straight Line", - # "total_number_of_depreciations": 3, - # "frequency_of_depreciation": 10, - # "depreciation_start_date": month_end_date - # }) - # asset_doc.submit() - - # expected_gle = ( - # ("_Test Fixed Asset - _TC", 5250.0, 0.0), - # ("CWIP Account - _TC", 0.0, 5250.0) - # ) - - # gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - # where voucher_type='Asset' and voucher_no = %s - # order by account""", asset_doc.name) - - - # self.assertEqual(gle, expected_gle) - - # def test_expense_head(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=2, rate=200000.0, location="Test Location") - - # doc = make_invoice(pr.name) - - # self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + def test_is_fixed_asset_set(self): + asset = create_asset(is_existing_asset = 1) + doc = frappe.new_doc('Purchase Invoice') + doc.supplier = '_Test Supplier' + doc.append('items', { + 'item_code': 'Macbook Pro', + 'qty': 1, + 'asset': asset.name + }) + + doc.set_missing_values() + self.assertEquals(doc.items[0].is_fixed_asset, 1) + + + def test_schedule_for_straight_line_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save() + + self.assertEqual(asset.status, "Draft") + expected_schedules = [ + ["2030-12-31", 30000.00, 30000.00], + ["2031-12-31", 30000.00, 60000.00], + ["2032-12-31", 30000.00, 90000.00] + ] + + 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): + create_asset(is_existing_asset=1) + asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + asset.calculate_depreciation = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 40000 + asset.available_for_use_date = "2030-06-06" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.insert() + self.assertEqual(asset.status, "Draft") + asset.save() + expected_schedules = [ + ["2030-12-31", 14246.58, 54246.58], + ["2031-12-31", 25000.00, 79246.58], + ["2032-06-06", 10753.42, 90000.00] + ] + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_double_declining_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Double Declining Balance", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": '2030-12-31' + }) + asset.insert() + self.assertEqual(asset.status, "Draft") + asset.save() + + expected_schedules = [ + ['2030-12-31', 66667.00, 66667.00], + ['2031-12-31', 22222.11, 88889.11], + ['2032-12-31', 1110.89, 90000.0] + ] + + 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): + create_asset(is_existing_asset = 1) + asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + asset.calculate_depreciation = 1 + asset.is_existing_asset = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 50000 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2029-11-30' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Double Declining Balance", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.insert() + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2030-12-31", 33333.50, 83333.50], + ["2031-12-31", 6666.50, 90000.0] + ] + + 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_prorated_straight_line_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.purchase_date = '2030-01-30' + asset.is_existing_asset = 0 + asset.available_for_use_date = "2030-01-30" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + + asset.insert() + asset.save() + + expected_schedules = [ + ["2030-12-31", 27534.25, 27534.25], + ["2031-12-31", 30000.0, 57534.25], + ["2032-12-31", 30000.0, 87534.25], + ["2033-01-30", 2465.75, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_depreciation(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.purchase_date = '2020-01-30' + asset.available_for_use_date = "2020-01-30" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.insert() + 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() + + # 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"), 0) + + def test_depreciation_entry_for_wdv_without_pro_rata(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 4000.00, 4000.00], + ["2031-12-31", 2000.00, 6000.00], + ["2032-12-31", 1000.00, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_pro_rata_depreciation_entry_for_wdv(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-06' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 2279.45, 2279.45], + ["2031-12-31", 2860.28, 5139.73], + ["2032-12-31", 1430.14, 6569.87], + ["2033-06-06", 430.13, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_depreciation_entry_cancellation(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.insert() + 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): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = nowdate() + asset.purchase_date = nowdate() + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": nowdate() + }) + asset.insert() + asset.submit() + + post_depreciation_entries(date=add_months(nowdate(), 10)) + + scrap_asset(asset.name) + + 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(asset.name) + + asset.load_from_db() + self.assertFalse(asset.journal_entry_for_scrap) + self.assertEqual(asset.status, "Partially Depreciated") + + def test_asset_sale(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.insert() + asset.submit() + post_depreciation_entries(date="2021-01-01") + + si = make_sales_invoice(asset=asset.name, 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", asset.name, "status"), "Sold") + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), + ("_Test Fixed Asset - _TC", 0.0, 100000.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 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", asset.name, "status"), "Partially Depreciated") + + def test_asset_expected_value_after_useful_life(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-06-06" + }) + asset.insert() + 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.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + + def test_cwip_accounting(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=5000, do_not_submit=True, location="Test Location") + + pr.set('taxes', [{ + 'category': 'Total', + 'add_deduct_tax': 'Add', + 'charge_type': 'On Net Total', + 'account_head': '_Test Account Service Tax - _TC', + 'description': '_Test Account Service Tax', + 'cost_center': 'Main - _TC', + 'rate': 5.0 + }, { + 'category': 'Valuation and Total', + 'add_deduct_tax': 'Add', + 'charge_type': 'On Net Total', + 'account_head': '_Test Account Shipping Charges - _TC', + 'description': '_Test Account Shipping Charges', + 'cost_center': 'Main - _TC', + 'rate': 5.0 + }]) + + pr.submit() + + expected_gle = ( + ("Asset Received But Not Billed - _TC", 0.0, 5250.0), + ("CWIP Account - _TC", 5250.0, 0.0) + ) + + pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Purchase Receipt' and voucher_no = %s + order by account""", pr.name) + + self.assertEqual(pr_gle, expected_gle) + + pi = make_invoice(pr.name) + pi.submit() + + expected_gle = ( + ("_Test Account Service Tax - _TC", 250.0, 0.0), + ("_Test Account Shipping Charges - _TC", 250.0, 0.0), + ("Asset Received But Not Billed - _TC", 5250.0, 0.0), + ("Creditors - _TC", 0.0, 5500.0), + ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), + ) + + pi_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(pi_gle, expected_gle) + + asset = frappe.db.get_value('Asset', + {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') + + asset_doc = frappe.get_doc('Asset', asset) + + month_end_date = get_last_day(nowdate()) + asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) + + asset_doc.append("finance_books", { + "expected_value_after_useful_life": 200, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + asset_doc.submit() + + expected_gle = ( + ("_Test Fixed Asset - _TC", 5250.0, 0.0), + ("CWIP Account - _TC", 0.0, 5250.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Asset' and voucher_no = %s + order by account""", asset_doc.name) + + + self.assertEqual(gle, expected_gle) + + def test_expense_head(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=2, rate=200000.0, location="Test Location") + + doc = make_invoice(pr.name) + + self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 14f3922c05..2a42894623 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -11,7 +11,6 @@ from frappe.model.document import Document class AssetCategory(Document): def validate(self): self.validate_finance_books() - self.validate_enable_cwip_accounting() def validate_finance_books(self): for d in self.finance_books: @@ -19,15 +18,6 @@ class AssetCategory(Document): if cint(d.get(frappe.scrub(field)))<1: frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) - def validate_enable_cwip_accounting(self): - if self.enable_cwip_accounting : - for d in self.accounts: - cwip = frappe.db.get_value("Company",d.company_name,"enable_cwip_accounting") - if cwip: - frappe.throw(_ - ("CWIP is enabled globally in Company {1}. To enable it in Asset Category, first disable it in {1} ").format( - frappe.bold(d.idx), frappe.bold(d.company_name))) - @frappe.whitelist() def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None): if item and frappe.db.get_value("Item", item, "is_fixed_asset"): diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json index a25b4ce82e..3236e726de 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json @@ -60,7 +60,8 @@ { "fieldname": "date", "fieldtype": "Date", - "label": "Date" + "label": "Date", + "reqd": 1 }, { "fieldname": "current_asset_value", @@ -110,7 +111,7 @@ } ], "is_submittable": 1, - "modified": "2019-05-26 09:46:23.613412", + "modified": "2019-11-22 14:09:25.800375", "modified_by": "Administrator", "module": "Assets", "name": "Asset Value Adjustment", diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 56425a0dcb..155597e856 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -5,12 +5,13 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, getdate, cint, date_diff +from frappe.utils import flt, getdate, cint, date_diff, formatdate from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from frappe.model.document import Document class AssetValueAdjustment(Document): def validate(self): + self.validate_date() self.set_difference_amount() self.set_current_asset_value() @@ -23,6 +24,12 @@ class AssetValueAdjustment(Document): frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry)) self.reschedule_depreciations(self.current_asset_value) + + def validate_date(self): + asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date') + if getdate(self.date) < getdate(asset_purchase_date): + frappe.throw(_("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.") + .format(formatdate(asset_purchase_date)), title="Incorrect Date") def set_difference_amount(self): self.difference_amount = flt(self.current_asset_value - self.new_asset_value) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index d0befcbcf3..3392850e96 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -577,6 +577,7 @@ class BuyingController(StockController): def auto_make_assets(self, asset_items): items_data = get_asset_item_details(asset_items) + messages = [] for d in self.items: if d.is_fixed_asset: @@ -589,12 +590,16 @@ class BuyingController(StockController): for qty in range(cint(d.qty)): self.make_asset(d) is_plural = 's' if cint(d.qty) != 1 else '' - frappe.msgprint(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural)) + messages.append(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural)) else: - frappe.throw(_("Asset Naming Series is mandatory for the auto creation for item {0}").format(d.item_code)) + frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}") + .format(d.item_code, d.idx)) else: - frappe.msgprint(_("Assets not created. You will have to create asset manually.")) - + messages.append(_("Assets not created for {0}. You will have to create asset manually.") + .format(d.item_code)) + + for message in messages: + frappe.msgprint(message, title="Success") def make_asset(self, row): if not row.asset_location: diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py index 5842e9edbf..4d4fc7c462 100644 --- a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py +++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py @@ -7,15 +7,11 @@ def execute(): '''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field in Company, delete Asset Settings ''' - if frappe.db.exists("DocType","Asset Settings"): - frappe.reload_doctype("Company") - cwip_value = frappe.db.get_single_value("Asset Settings","disable_cwip_accounting") + if frappe.db.exists("DocType", "Asset Settings"): + frappe.reload_doctype("Asset Category") + cwip_value = frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting") + + frappe.db.sql("""UPDATE `tabAsset Category` SET enable_cwip_accounting = %s""", cint(cwip_value)) - companies = [x['name'] for x in frappe.get_all("Company", "name")] - for company in companies: - enable_cwip_accounting = cint(not cint(cwip_value)) - frappe.db.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting) - - frappe.db.sql( - """ DELETE FROM `tabSingles` where doctype = 'Asset Settings' """) - frappe.delete_doc_if_exists("DocType","Asset Settings") \ No newline at end of file + frappe.db.sql("""DELETE FROM `tabSingles` where doctype = 'Asset Settings'""") + frappe.delete_doc_if_exists("DocType", "Asset Settings") \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 2d181b53ca..dd602eca10 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -72,7 +72,6 @@ "stock_received_but_not_billed", "expenses_included_in_valuation", "fixed_asset_depreciation_settings", - "enable_cwip_accounting", "accumulated_depreciation_account", "depreciation_expense_account", "series_for_depreciation_entry", @@ -721,18 +720,12 @@ "fieldtype": "Link", "label": "Default Buying Terms", "options": "Terms and Conditions" - }, - { - "default": "0", - "fieldname": "enable_cwip_accounting", - "fieldtype": "Check", - "label": "Enable Capital Work in Progress Accounting" } ], "icon": "fa fa-building", "idx": 1, "image_field": "company_logo", - "modified": "2019-10-09 14:42:04.440974", + "modified": "2019-11-22 13:04:47.470768", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 2f4abbcea6..410d9f1b45 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -49,7 +49,7 @@ frappe.ui.form.on("Item", { if (!frm.doc.is_fixed_asset) { erpnext.item.make_dashboard(frm); } - + if (frm.doc.is_fixed_asset) { frm.trigger('is_fixed_asset'); frm.trigger('auto_create_assets'); @@ -140,6 +140,7 @@ frappe.ui.form.on("Item", { // set serial no to false & toggles its visibility frm.set_value('has_serial_no', 0); frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); + frm.toggle_reqd(['asset_category'], frm.doc.is_fixed_asset); frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); frm.call({ @@ -150,6 +151,8 @@ frappe.ui.form.on("Item", { frm.trigger("set_asset_naming_series"); } }); + + frm.trigger('auto_create_assets'); }, set_asset_naming_series: function(frm) { @@ -159,8 +162,8 @@ frappe.ui.form.on("Item", { }, auto_create_assets: function(frm) { - frm.toggle_reqd(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets); - frm.toggle_display(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets); + frm.toggle_reqd(['asset_naming_series'], frm.doc.auto_create_assets); + frm.toggle_display(['asset_naming_series'], frm.doc.auto_create_assets); }, page_name: frappe.utils.warn_page_name_change, diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 173b394f79..7df40fb02c 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -138,8 +138,8 @@ class LandedCostVoucher(Document): if item.is_fixed_asset: receipt_document_type = 'purchase_invoice' if item.receipt_document_type == 'Purchase Invoice' \ else 'purchase_receipt' - docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document }, - fields=['name', 'docstatus']) + docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document, + 'item_code': item.item_code }, fields=['name', 'docstatus']) if not docs or len(docs) != item.qty: frappe.throw(_('There are not enough asset created or linked to {0}. \ Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty)) @@ -148,8 +148,7 @@ class LandedCostVoucher(Document): if d.docstatus == 1: frappe.throw(_('{2} {0} has submitted Assets.\ Remove Item {1} from table to continue.').format( - item.receipt_document, item.item_code, item.receipt_document_type) - ) + item.receipt_document, item.item_code, item.receipt_document_type)) def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): for item in receipt_document.get("items"): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0cb21d73f9..d0fae6a227 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -82,11 +82,21 @@ class PurchaseReceipt(BuyingController): self.validate_with_previous_doc() self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") + self.validate_cwip_accounts() self.check_on_hold_or_closed_status() if getdate(self.posting_date) > getdate(nowdate()): throw(_("Posting Date cannot be future date")) + + def validate_cwip_accounts(self): + for item in self.get('items'): + if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category): + # check cwip accounts before making auto assets + # Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account + arbnb_account = self.get_company_default("asset_received_but_not_billed") + cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) + break def validate_with_previous_doc(self): super(PurchaseReceipt, self).validate_with_previous_doc({ @@ -343,7 +353,7 @@ class PurchaseReceipt(BuyingController): def get_asset_gl_entry(self, gl_entries): for item in self.get("items"): if item.is_fixed_asset: - if is_cwip_accounting_enabled(self.company, item.asset_category): + if is_cwip_accounting_enabled(item.asset_category): self.add_asset_gl_entries(item, gl_entries) if flt(item.landed_cost_voucher_amount): self.add_lcv_gl_entries(item, gl_entries) @@ -386,7 +396,7 @@ class PurchaseReceipt(BuyingController): def add_lcv_gl_entries(self, item, gl_entries): expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") - if not is_cwip_accounting_enabled(self.company, item.asset_category): + if not is_cwip_accounting_enabled(item.asset_category): asset_account = get_asset_category_account(asset_category=item.asset_category, \ fieldname='fixed_asset_account', company=self.company) else: From cd3976f7d24a703c80858237e9f51569b9323bea Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 25 Nov 2019 12:24:34 +0530 Subject: [PATCH 128/131] Asset cancellation fix (#19671) * fix: remove asset movement mandatory fields * fix: label for reference doctype --- erpnext/assets/doctype/asset/asset.py | 25 +++------ .../doctype/asset_movement/asset_movement.js | 51 +++---------------- .../asset_movement/asset_movement.json | 15 +++--- .../doctype/asset_movement/asset_movement.py | 2 +- erpnext/controllers/buying_controller.py | 13 +++-- 5 files changed, 32 insertions(+), 74 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 546f374094..56341ed1b1 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -125,12 +125,14 @@ class Asset(AccountsController): frappe.throw(_("Available-for-use Date should be after purchase date")) def cancel_auto_gen_movement(self): - reference_docname = self.purchase_invoice or self.purchase_receipt - movement = frappe.db.get_all('Asset Movement', filters={ 'reference_name': reference_docname, 'docstatus': 1 }) - if len(movement) > 1: + movements = frappe.db.sql( + """SELECT asm.name, asm.docstatus + FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item + WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1) + if len(movements) > 1: frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \ cancelled manually to cancel this asset.')) - movement = frappe.get_doc('Asset Movement', movement[0].get('name')) + movement = frappe.get_doc('Asset Movement', movements[0].get('name')) movement.flags.ignore_validate = True movement.cancel() @@ -658,23 +660,10 @@ def make_asset_movement(assets, purpose=None): frappe.throw(_('Atleast one asset has to be selected.')) asset_movement = frappe.new_doc("Asset Movement") - asset_movement.purpose = purpose - prev_reference_docname = '' - + asset_movement.quantity = len(assets) for asset in assets: asset = frappe.get_doc('Asset', asset.get('name')) - # get PR/PI linked with asset - reference_docname = asset.get('purchase_receipt') if asset.get('purchase_receipt') \ - else asset.get('purchase_invoice') - # checks if all the assets are linked with a single PR/PI - if prev_reference_docname == '': - prev_reference_docname = reference_docname - elif prev_reference_docname != reference_docname: - frappe.throw(_('Assets selected should belong to same reference document.')) - asset_movement.company = asset.get('company') - asset_movement.reference_doctype = 'Purchase Receipt' if asset.get('purchase_receipt') else 'Purchase Invoice' - asset_movement.reference_name = prev_reference_docname asset_movement.append("assets", { 'asset': asset.get('name'), 'source_location': asset.get('location'), diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index 89977e2952..06d8879091 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -31,6 +31,13 @@ frappe.ui.form.on('Asset Movement', { name: ["in", ["Purchase Receipt", "Purchase Invoice"]] } }; + }), + frm.set_query("asset", "assets", () => { + return { + filters: { + status: ["not in", ["Draft"]] + } + } }) }, @@ -76,50 +83,6 @@ frappe.ui.form.on('Asset Movement', { }); }); frm.refresh_field('assets'); - }, - - reference_name: function(frm) { - if (frm.doc.reference_name && frm.doc.reference_doctype) { - const reference_doctype = frm.doc.reference_doctype === 'Purchase Invoice' ? 'purchase_invoice' : 'purchase_receipt'; - // On selection of reference name, - // sets query to display assets linked to that reference doc - frm.set_query('asset', 'assets', function() { - return { - filters: { - [reference_doctype] : frm.doc.reference_name - } - }; - }); - - // fetches linked asset & adds to the assets table - frappe.db.get_list('Asset', { - fields: ['name', 'location', 'custodian'], - filters: { - [reference_doctype] : frm.doc.reference_name - } - }).then((docs) => { - if (docs.length == 0) { - frappe.msgprint(frappe._(`Please select ${frm.doc.reference_doctype} which has assets.`)); - frm.doc.reference_name = ''; - frm.refresh_field('reference_name'); - return; - } - frm.doc.assets = []; - docs.forEach(doc => { - frm.add_child('assets', { - asset: doc.name, - source_location: doc.location, - from_employee: doc.custodian - }); - frm.refresh_field('assets'); - }) - }).catch((err) => { - console.log(err); // eslint-disable-line - }); - } else { - // if reference is deleted then remove query - frm.set_query('asset', 'assets', () => ({ filters: {} })); - } } }); diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index e62d684411..3472ab5d7d 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -9,12 +9,12 @@ "purpose", "column_break_4", "transaction_date", + "section_break_10", + "assets", "reference", "reference_doctype", "column_break_9", "reference_name", - "section_break_10", - "assets", "amended_from" ], "fields": [ @@ -47,6 +47,7 @@ "fieldtype": "Column Break" }, { + "collapsible": 1, "fieldname": "reference", "fieldtype": "Section Break", "label": "Reference" @@ -54,18 +55,16 @@ { "fieldname": "reference_doctype", "fieldtype": "Link", - "label": "Reference Document", + "label": "Reference Document Type", "no_copy": 1, - "options": "DocType", - "reqd": 1 + "options": "DocType" }, { "fieldname": "reference_name", "fieldtype": "Dynamic Link", "label": "Reference Document Name", "no_copy": 1, - "options": "reference_doctype", - "reqd": 1 + "options": "reference_doctype" }, { "fieldname": "amended_from", @@ -93,7 +92,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-21 14:35:51.880332", + "modified": "2019-11-23 13:28:47.256935", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 714845dfac..4e1822b2ce 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -22,7 +22,7 @@ class AssetMovement(Document): if company != self.company: frappe.throw(_("Asset {0} does not belong to company {1}").format(d.asset, self.company)) - if not(d.source_location or d.target_location or d.from_employee or d.to_employee): + if not (d.source_location or d.target_location or d.from_employee or d.to_employee): frappe.throw(_("Either location or employee must be required")) def validate_location(self): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 3392850e96..d12643af82 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -641,7 +641,10 @@ class BuyingController(StockController): asset = frappe.get_doc('Asset', asset.name) if delete_asset and is_auto_create_enabled: # need to delete movements to delete assets otherwise throws link exists error - movements = frappe.db.get_all('Asset Movement', filters={ 'reference_name': self.name }) + movements = frappe.db.sql( + """SELECT asm.name + FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item + WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1) for movement in movements: frappe.delete_doc('Asset Movement', movement.name, force=1) frappe.delete_doc("Asset", asset.name, force=1) @@ -652,8 +655,12 @@ class BuyingController(StockController): asset.purchase_date = self.posting_date asset.supplier = self.supplier elif self.docstatus == 2: - asset.set(field, None) - asset.supplier = None + if asset.docstatus == 0: + asset.set(field, None) + asset.supplier = None + if asset.docstatus == 1 and delete_asset: + frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\ + Please cancel the it to continue.').format(asset.name)) asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_mandatory = True From 35e8d1e1d7381bcbd92a50f2798c0fdb9dae4fc4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 25 Nov 2019 14:02:51 +0530 Subject: [PATCH 129/131] Stock acc bal sync msg (#19676) * fix: Prefilled JV via Account Balance and Stock Value mismatch error message - Make JV button will route to Journal Entry and add rows in child table * fix: make journal entry to sync stock and account balance * fix: translated action label --- erpnext/accounts/general_ledger.py | 34 +++++++++++++++++------ erpnext/public/js/controllers/accounts.js | 4 +-- erpnext/public/js/utils.js | 16 +++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index e9703dd790..2ba319d05e 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -163,16 +163,32 @@ def validate_account_for_perpetual_inventory(gl_map): .format(account), StockAccountInvalidTransaction) elif account_bal != stock_bal: - error_reason = _("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and it's linked warehouses.").format( - account_bal, stock_bal, frappe.bold(account)) - error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(stock_bal - account_bal)) - button_text = _("Make Adjustment Entry") + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), + currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) - frappe.throw("""{0}

      {1}

      -
      - -
      """.format(error_reason, error_resolution, button_text), - StockValueAndAccountBalanceOutOfSync, title=_('Account Balance Out Of Sync')) + diff = flt(stock_bal - account_bal, precision) + error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( + stock_bal, account_bal, frappe.bold(account)) + error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) + stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") + + db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') + db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') + + journal_entry_args = { + 'accounts':[ + {'account': account, db_or_cr_warehouse_account : abs(diff)}, + {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] + } + + frappe.msgprint(msg="""{0}

      {1}

      """.format(error_reason, error_resolution), + raise_exception=StockValueAndAccountBalanceOutOfSync, + title=_('Values Out Of Sync'), + primary_action={ + 'label': _('Make Journal Entry'), + 'client_action': 'erpnext.route_to_adjustment_jv', + 'args': journal_entry_args + }) def validate_cwip_accounts(gl_map): cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 3dfc8911fc..f4eaad58da 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -64,7 +64,7 @@ frappe.ui.form.on(cur_frm.doctype, { } }) } - } + } }); frappe.ui.form.on('Sales Invoice Payment', { @@ -355,4 +355,4 @@ cur_frm.pformat.taxes= function(doc){ out += '
    '; } return out; -} +} \ No newline at end of file diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6f43d9ef8c..d5a78d4f1f 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -74,6 +74,22 @@ $.extend(erpnext, { ); }); }, + + route_to_adjustment_jv: (args) => { + frappe.model.with_doctype('Journal Entry', () => { + // route to adjustment Journal Entry to handle Account Balance and Stock Value mismatch + let journal_entry = frappe.model.get_new_doc('Journal Entry'); + + args.accounts.forEach((je_account) => { + let child_row = frappe.model.add_child(journal_entry, "accounts"); + child_row.account = je_account.account; + child_row.debit_in_account_currency = je_account.debit_in_account_currency; + child_row.credit_in_account_currency = je_account.credit_in_account_currency; + child_row.party_type = "" ; + }); + frappe.set_route('Form','Journal Entry', journal_entry.name); + }); + } }); From c856cb85d9e443f00ee306428ab69e4b8aa88049 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 25 Nov 2019 15:08:24 +0530 Subject: [PATCH 130/131] log: Change log for v12.2.0 --- erpnext/change_log/v12/v12_2_0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 erpnext/change_log/v12/v12_2_0.md diff --git a/erpnext/change_log/v12/v12_2_0.md b/erpnext/change_log/v12/v12_2_0.md new file mode 100644 index 0000000000..0ec0eeca3d --- /dev/null +++ b/erpnext/change_log/v12/v12_2_0.md @@ -0,0 +1,14 @@ +# Version 12.2.0 Release Notes + +### Accounting + +1. Fixed Asset + - "Enable CWIP" options moved to Asset Category from Asset Settings + - Removed Asset link from Purchase Receipt Item table + - Enhanced Asset master + - Asset Movement now handles movement of multiple assets + - Introduced monthly depreciation +2. GL Entries for Landed Cost Voucher now posted directly against individual Charges account +3. Optimization of BOM Update Tool +4. Syncing of Stock and Account balance is enforced, in case of perpetual inventory +5. Rendered email template in Email Campaign From 4d12f8acab13add3bff3bdf30f8d640d60e16dfc Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 25 Nov 2019 15:31:23 +0550 Subject: [PATCH 131/131] bumped to version 12.2.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index d031bc5bb1..f40b957563 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.1.8' +__version__ = '12.2.0' def get_default_company(user=None): '''Get default company for user'''