diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b9846c8f57..9660cb9688 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe -__version__ = '7.1.15' +__version__ = '7.1.16' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/asset/asset.js b/erpnext/accounts/doctype/asset/asset.js index 8ff4b83d68..664ed4da25 100644 --- a/erpnext/accounts/doctype/asset/asset.js +++ b/erpnext/accounts/doctype/asset/asset.js @@ -28,6 +28,7 @@ frappe.ui.form.on('Asset', { refresh: function(frm) { frappe.ui.form.trigger("Asset", "is_existing_asset"); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); + frm.events.make_schedules_editable(frm); if (frm.doc.docstatus==1) { if (frm.doc.status=='Submitted' && !frm.doc.is_existing_asset && !frm.doc.purchase_invoice) { @@ -141,6 +142,22 @@ frappe.ui.form.on('Asset', { frm.toggle_enable("supplier", frm.doc.is_existing_asset); frm.toggle_reqd("next_depreciation_date", !frm.doc.is_existing_asset); }, + + opening_accumulated_depreciation: function(frm) { + erpnext.asset.set_accululated_depreciation(frm); + }, + + depreciation_method: function(frm) { + frm.events.make_schedules_editable(frm); + }, + + make_schedules_editable: function(frm) { + var is_editable = frm.doc.depreciation_method==="Manual" ? true : false; + frm.toggle_enable("schedules", is_editable); + frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); + frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable); + } + }); frappe.ui.form.on('Depreciation Schedule', { @@ -159,9 +176,25 @@ frappe.ui.form.on('Depreciation Schedule', { } }) } + }, + + depreciation_amount: function(frm, cdt, cdn) { + erpnext.asset.set_accululated_depreciation(frm); } + }) +erpnext.asset.set_accululated_depreciation = function(frm) { + if(frm.doc.depreciation_method != "Manual") return; + + accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation); + $.each(frm.doc.schedules || [], function(i, row) { + accumulated_depreciation += flt(row.depreciation_amount); + frappe.model.set_value(row.doctype, row.name, + "accumulated_depreciation_amount", accumulated_depreciation); + }) +} + erpnext.asset.make_purchase_invoice = function(frm) { frappe.call({ args: { diff --git a/erpnext/accounts/doctype/asset/asset.json b/erpnext/accounts/doctype/asset/asset.json index d3a88fbaa4..95d9b48603 100644 --- a/erpnext/accounts/doctype/asset/asset.json +++ b/erpnext/accounts/doctype/asset/asset.json @@ -516,7 +516,7 @@ "columns": 0, "fieldname": "value_after_depreciation", "fieldtype": "Currency", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -580,7 +580,7 @@ "label": "Depreciation Method", "length": 0, "no_copy": 0, - "options": "\nStraight Line\nDouble Declining Balance", + "options": "\nStraight Line\nDouble Declining Balance\nManual", "permlevel": 0, "precision": "", "print_hide": 0, @@ -750,7 +750,7 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -797,7 +797,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-03 14:58:53.710357", + "modified": "2016-11-18 15:59:19.774500", "modified_by": "Administrator", "module": "Accounts", "name": "Asset", diff --git a/erpnext/accounts/doctype/asset/asset.py b/erpnext/accounts/doctype/asset/asset.py index da73218b25..9caac071f9 100644 --- a/erpnext/accounts/doctype/asset/asset.py +++ b/erpnext/accounts/doctype/asset/asset.py @@ -18,6 +18,7 @@ class Asset(Document): self.set_missing_values() self.validate_asset_values() self.make_depreciation_schedule() + self.set_accumulated_depreciation() self.validate_expected_value_after_useful_life() # Validate depreciation related accounts get_depreciation_accounts(self) @@ -48,7 +49,7 @@ class Asset(Document): for field, value in item_details.items(): if not self.get(field): self.set(field, value) - + self.value_after_depreciation = (flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)) @@ -87,9 +88,10 @@ class Asset(Document): frappe.throw(_("Please set Next Depreciation Date")) def make_depreciation_schedule(self): - self.schedules = [] + if self.depreciation_method != 'Manual': + self.schedules = [] + if not self.get("schedules") and self.next_depreciation_date: - accumulated_depreciation = flt(self.opening_accumulated_depreciation) value_after_depreciation = flt(self.value_after_depreciation) number_of_pending_depreciations = cint(self.total_number_of_depreciations) - \ @@ -100,18 +102,21 @@ class Asset(Document): n * cint(self.frequency_of_depreciation)) depreciation_amount = self.get_depreciation_amount(value_after_depreciation) - - accumulated_depreciation += flt(depreciation_amount) value_after_depreciation -= flt(depreciation_amount) self.append("schedules", { "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "accumulated_depreciation_amount": accumulated_depreciation + "depreciation_amount": depreciation_amount }) + + def set_accumulated_depreciation(self): + accumulated_depreciation = flt(self.opening_accumulated_depreciation) + for d in self.get("schedules"): + accumulated_depreciation += flt(d.depreciation_amount) + d.accumulated_depreciation_amount = accumulated_depreciation def get_depreciation_amount(self, depreciable_value): - if self.depreciation_method == "Straight Line": + if self.depreciation_method in ("Straight Line", "Manual"): depreciation_amount = (flt(self.value_after_depreciation) - flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) @@ -126,16 +131,15 @@ class Asset(Document): return depreciation_amount def validate_expected_value_after_useful_life(self): - if self.depreciation_method == "Double Declining Balance": - accumulated_depreciation_after_full_schedule = \ - max([d.accumulated_depreciation_amount for d in self.get("schedules")]) - - asset_value_after_full_schedule = (flt(self.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule)) - - if self.expected_value_after_useful_life < asset_value_after_full_schedule: - frappe.throw(_("Expected value after useful life must be greater than or equal to {0}") - .format(asset_value_after_full_schedule)) + accumulated_depreciation_after_full_schedule = \ + max([d.accumulated_depreciation_amount for d in self.get("schedules")]) + + asset_value_after_full_schedule = (flt(self.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule)) + + if self.expected_value_after_useful_life < asset_value_after_full_schedule: + frappe.throw(_("Expected value after useful life must be greater than or equal to {0}") + .format(asset_value_after_full_schedule)) def validate_cancellation(self): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): diff --git a/erpnext/accounts/doctype/asset/test_asset.py b/erpnext/accounts/doctype/asset/test_asset.py index b409ec3a25..51496b918c 100644 --- a/erpnext/accounts/doctype/asset/test_asset.py +++ b/erpnext/accounts/doctype/asset/test_asset.py @@ -119,6 +119,30 @@ class TestAsset(unittest.TestCase): for d in asset.get("schedules")] self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_manual_method(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.depreciation_method = "Manual" + asset.schedules = [] + for schedule_date, amount in [["2020-12-31", 40000], ["2021-06-30", 30000], ["2021-10-31", 20000]]: + asset.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": amount + }) + asset.save() + + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2020-12-31", 40000, 40000], + ["2021-06-30", 30000, 70000], + ["2021-10-31", 20000, 90000] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) def test_depreciation(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js index 85d3187131..f1ccd9f8c3 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js @@ -46,6 +46,12 @@ frappe.ui.form.on("Bank Reconciliation", { callback: function(r, rt) { frm.refresh_field("payment_entries"); frm.refresh_fields(); + + $(frm.fields_dict.payment_entries.wrapper).find("[data-fieldname=amount]").each(function(i,v){ + if (i !=0){ + $(v).addClass("text-right") + } + }) } }); } diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index a56170d992..abc6ebaf9c 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, getdate, nowdate +from frappe.utils import flt, getdate, nowdate, fmt_money from frappe import msgprint, _ from frappe.model.document import Document @@ -26,8 +26,8 @@ class BankReconciliation(Document): select "Journal Entry" as payment_document, t1.name as payment_entry, t1.cheque_no as cheque_number, t1.cheque_date, - abs(t2.debit_in_account_currency - t2.credit_in_account_currency) as amount, - t1.posting_date, t2.against_account, t1.clearance_date + t2.debit_in_account_currency as debit, t2.credit_in_account_currency as credit, + t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency from `tabJournal Entry` t1, `tabJournal Entry Account` t2 where @@ -36,21 +36,23 @@ class BankReconciliation(Document): and ifnull(t1.is_opening, 'No') = 'No' {0} order by t1.posting_date ASC, t1.name DESC """.format(condition), (self.bank_account, self.from_date, self.to_date), as_dict=1) - + payment_entries = frappe.db.sql(""" select "Payment Entry" as payment_document, name as payment_entry, reference_no as cheque_number, reference_date as cheque_date, - if(paid_from=%s, paid_amount, received_amount) as amount, - posting_date, party as against_account, clearance_date + if(paid_from=%(account)s, paid_amount, "") as credit, + if(paid_from=%(account)s, "", received_amount) as debit, + posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, + if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency from `tabPayment Entry` where - (paid_from=%s or paid_to=%s) and docstatus=1 - and posting_date >= %s and posting_date <= %s {0} + (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 + and posting_date >= %(from)s and posting_date <= %(to)s {0} order by posting_date ASC, name DESC """.format(condition), - (self.bank_account, self.bank_account, self.bank_account, self.from_date, self.to_date), as_dict=1) + {"account":self.bank_account, "from":self.from_date, "to":self.to_date}, as_dict=1) entries = sorted(list(payment_entries)+list(journal_entries), key=lambda k: k['posting_date'] or getdate(nowdate())) @@ -60,6 +62,11 @@ class BankReconciliation(Document): for d in entries: row = self.append('payment_entries', {}) + + d.amount = fmt_money(d.debit if d.debit else d.credit, 2, d.account_currency) + " " + (_("Dr") if d.debit else _("Cr")) + d.pop("credit") + d.pop("debit") + d.pop("account_currency") row.update(d) self.total_amount += flt(d.amount) diff --git a/erpnext/accounts/doctype/bank_reconciliation_detail/bank_reconciliation_detail.json b/erpnext/accounts/doctype/bank_reconciliation_detail/bank_reconciliation_detail.json index 0f30feefa8..2e4af267ee 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_detail/bank_reconciliation_detail.json +++ b/erpnext/accounts/doctype/bank_reconciliation_detail/bank_reconciliation_detail.json @@ -40,14 +40,14 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 1, "fieldname": "payment_entry", "fieldtype": "Dynamic Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, + "in_list_view": 1, "label": "Payment Entry", "length": 0, "no_copy": 0, @@ -69,7 +69,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 3, + "columns": 2, "fieldname": "against_account", "fieldtype": "Data", "hidden": 0, @@ -99,7 +99,7 @@ "collapsible": 0, "columns": 2, "fieldname": "amount", - "fieldtype": "Currency", + "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -110,7 +110,7 @@ "no_copy": 0, "oldfieldname": "debit", "oldfieldtype": "Currency", - "options": "account_currency", + "options": "", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -151,7 +151,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -178,7 +178,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 3, + "columns": 1, "fieldname": "cheque_number", "fieldtype": "Data", "hidden": 0, @@ -267,7 +267,7 @@ "istable": 1, "max_attachments": 0, "menu_index": 0, - "modified": "2016-08-26 01:51:36.123941", + "modified": "2016-11-17 11:39:00.308624", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Reconciliation Detail", diff --git a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json index 57c14b7974..1fadf5eb4f 100644 --- a/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/accounts/doctype/depreciation_schedule/depreciation_schedule.json @@ -10,11 +10,13 @@ "doctype": "DocType", "document_type": "Document", "editable_grid": 1, + "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "schedule_date", "fieldtype": "Date", "hidden": 0, @@ -29,7 +31,8 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -40,6 +43,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "depreciation_amount", "fieldtype": "Currency", "hidden": 0, @@ -55,7 +59,8 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -66,6 +71,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, @@ -80,6 +86,7 @@ "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, @@ -90,6 +97,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "accumulated_depreciation_amount", "fieldtype": "Currency", "hidden": 0, @@ -106,8 +114,9 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -116,6 +125,8 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.docstatus==1", "fieldname": "journal_entry", "fieldtype": "Link", "hidden": 0, @@ -132,6 +143,7 @@ "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, @@ -142,7 +154,8 @@ "allow_on_submit": 1, "bold": 0, "collapsible": 0, - "depends_on": "eval:(!doc.journal_entry && doc.schedule_date <= get_today())", + "columns": 0, + "depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())", "fieldname": "make_depreciation_entry", "fieldtype": "Button", "hidden": 0, @@ -158,6 +171,7 @@ "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, @@ -175,7 +189,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-07-11 03:27:59.603924", + "modified": "2016-11-18 16:42:19.543657", "modified_by": "Administrator", "module": "Accounts", "name": "Depreciation Schedule", diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json index 377a95c7d5..7af7f48ce7 100644 --- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json +++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json @@ -9,11 +9,14 @@ "description": "**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.", "docstatus": 0, "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Name of the Monthly Distribution", "fieldname": "distribution_id", "fieldtype": "Data", @@ -31,6 +34,7 @@ "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, @@ -41,6 +45,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "fiscal_year", "fieldtype": "Link", "hidden": 0, @@ -58,6 +63,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 1, @@ -68,6 +74,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "percentages", "fieldtype": "Table", "hidden": 0, @@ -85,6 +92,7 @@ "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, @@ -96,13 +104,14 @@ "hide_toolbar": 0, "icon": "icon-bar-chart", "idx": 1, + "image_view": 0, "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-05-16 16:35:20.349194", + "modified": "2016-11-21 14:54:35.998761", "modified_by": "Administrator", "module": "Accounts", "name": "Monthly Distribution", @@ -119,6 +128,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -139,6 +149,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 2, "print": 0, "read": 1, @@ -150,7 +161,7 @@ "write": 0 } ], - "quick_entry": 1, + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", diff --git a/erpnext/accounts/doctype/pos_customer_group/__init__.py b/erpnext/accounts/doctype/pos_customer_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json new file mode 100644 index 0000000000..4f6a675fb6 --- /dev/null +++ b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json @@ -0,0 +1,66 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-11-16 15:27:16.413449", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Customer Group", + "length": 0, + "no_copy": 0, + "options": "Customer Group", + "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 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-11-16 15:27:25.730507", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Customer Group", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py new file mode 100644 index 0000000000..85c1c9f8dd --- /dev/null +++ b/erpnext/accounts/doctype/pos_customer_group/pos_customer_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class POSCustomerGroup(Document): + pass diff --git a/erpnext/accounts/doctype/pos_item_group/__init__.py b/erpnext/accounts/doctype/pos_item_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_item_group/pos_item_group.json b/erpnext/accounts/doctype/pos_item_group/pos_item_group.json new file mode 100644 index 0000000000..b278765234 --- /dev/null +++ b/erpnext/accounts/doctype/pos_item_group/pos_item_group.json @@ -0,0 +1,66 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-11-16 15:26:47.706713", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "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 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-11-16 15:27:32.263630", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Item Group", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_item_group/pos_item_group.py b/erpnext/accounts/doctype/pos_item_group/pos_item_group.py new file mode 100644 index 0000000000..ceaa57ba60 --- /dev/null +++ b/erpnext/accounts/doctype/pos_item_group/pos_item_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class POSItemGroup(Document): + pass diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index c1aa0c3cb5..bbbab73e1c 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -26,6 +26,30 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { }); }); +frappe.ui.form.on("POS Profile", { + setup: function(frm) { + frm.trigger("get_query_for_groups") + }, + + get_query_for_groups: function(frm) { + frm.fields_dict['item_groups'].grid.get_field('item_group').get_query = function(frm, cdt, cdn) { + return{ + filters: { + 'is_group': 0 + } + } + } + + frm.fields_dict['customer_groups'].grid.get_field('customer_group').get_query = function(frm, cdt, cdn) { + return{ + filters: { + 'is_group': 0 + } + } + } + } +}) + // Income Account // -------------------------------- cur_frm.fields_dict['income_account'].get_query = function(doc,cdt,cdn) { diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index f450121817..e6cfd7409b 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -375,6 +375,114 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_14", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_groups", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Item Groups", + "length": 0, + "no_copy": 0, + "options": "POS Item Group", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "customer_groups", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Customer Groups", + "length": 0, + "no_copy": 0, + "options": "POS Customer Group", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -543,34 +651,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -951,8 +1031,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-03 15:53:33.820428", - "modified_by": "Administrator", + "modified": "2016-11-17 00:20:51.377850", + "modified_by": "rohit@erpnext.com", "module": "Accounts", "name": "POS Profile", "owner": "Administrator", diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 5f4d5bc75d..ef497bfe29 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -13,6 +13,7 @@ class POSProfile(Document): def validate(self): self.check_for_duplicate() self.validate_all_link_fields() + self.validate_duplicate_groups() def check_for_duplicate(self): res = frappe.db.sql("""select name, user from `tabPOS Profile` @@ -37,6 +38,16 @@ class POSProfile(Document): "company": self.company, "name": link_dn}): frappe.throw(_("{0} does not belong to Company {1}").format(link_dn, self.company)) + def validate_duplicate_groups(self): + item_groups = [d.item_group for d in self.item_groups] + customer_groups = [d.customer_group for d in self.customer_groups] + + if len(item_groups) != len(set(item_groups)): + frappe.throw(_("Duplicate item group found in the item group table"), title = "Duplicate Item Group") + + if len(customer_groups) != len(set(customer_groups)): + frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group") + def before_save(self): set_account_for_mode_of_payment(self) diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 62274a332f..9c6a11487c 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -5,8 +5,47 @@ from __future__ import unicode_literals import frappe import unittest - -# test_records = frappe.get_test_records('POS Profile') +from erpnext.stock.get_item_details import get_pos_profile +from erpnext.accounts.doctype.sales_invoice.pos import get_items_list, get_customers_list class TestPOSProfile(unittest.TestCase): - pass + def test_pos_profile(self): + make_pos_profile() + + pos_profile = get_pos_profile("_Test Company") or {} + if pos_profile: + doc = frappe.get_doc("POS Profile", pos_profile.get("name")) + doc.append('item_groups', {'item_group': '_Test Item Group'}) + doc.append('customer_groups', {'customer_group': '_Test Customer Group'}) + doc.save() + + items = get_items_list(doc) + customers = get_customers_list(doc) + + products_count = frappe.db.sql(""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1) + customers_count = frappe.db.sql(""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'""") + + self.assertEquals(len(items), products_count[0][0]) + self.assertEquals(len(customers), customers_count[0][0]) + + frappe.db.sql("delete from `tabPOS Profile`") + +def make_pos_profile(): + pos_profile = frappe.get_doc({ + "company": "_Test Company", + "cost_center": "_Test Cost Center - _TC", + "currency": "INR", + "doctype": "POS Profile", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "income_account": "Sales - _TC", + "name": "_Test POS Profile", + "naming_series": "_T-POS Profile-", + "selling_price_list": "_Test Price List", + "territory": "_Test Territory", + "warehouse": "_Test Warehouse - _TC", + "write_off_account": "_Test Write Off - _TC", + "write_off_cost_center": "_Test Write Off Cost Center - _TC" + }) + + if not frappe.db.exists("POS Profile", "_Test POS Profile"): + pos_profile.insert() \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 1d1a122011..e0a81216c8 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -30,10 +30,16 @@ def get_pos_data(): return { 'doc': doc, 'default_customer': pos_profile.get('customer'), - 'items': get_items(doc, pos_profile), - 'customers': get_customers(pos_profile, doc, company_data.default_currency), - 'pricing_rules': get_pricing_rules(doc), + 'items': get_items_list(pos_profile), + 'customers': get_customers_list(pos_profile), + 'serial_no_data': get_serial_no_data(pos_profile, doc.company), + 'batch_no_data': get_batch_no_data(), + 'tax_data': get_item_tax_data(), + 'price_list_data': get_price_list_data(doc.selling_price_list), + 'bin_data': get_bin_data(pos_profile), + 'pricing_rules': get_pricing_rule_data(doc), 'print_template': print_template, + 'pos_profile': pos_profile, 'meta': { 'invoice': frappe.get_meta('Sales Invoice'), 'items': frappe.get_meta('Sales Invoice Item'), @@ -104,63 +110,117 @@ def update_tax_table(doc): for tax in taxes: doc.append('taxes', tax) -def get_items(doc, pos_profile): - item_list = [] - for item in frappe.get_all("Item", fields=["*"], filters={'disabled': 0, 'has_variants': 0, 'is_sales_item': 1}): - item_doc = frappe.get_doc('Item', item.name) - if item_doc.taxes: - item.taxes = json.dumps(dict(([d.tax_type, d.tax_rate] for d in - item_doc.get("taxes")))) +def get_items_list(pos_profile): + cond = "1=1" + item_groups = [] + if pos_profile.get('item_groups'): + # Get items based on the item groups defined in the POS profile - item.price_list_rate = frappe.db.get_value('Item Price', {'item_code': item.name, - 'price_list': doc.selling_price_list}, 'price_list_rate') or 0 - item.default_warehouse = pos_profile.get('warehouse') or \ - get_item_warehouse_for_company(doc.company, item.default_warehouse) or None - item.expense_account = pos_profile.get('expense_account') or item.expense_account - item.income_account = pos_profile.get('income_account') or item_doc.income_account - item.cost_center = pos_profile.get('cost_center') or item_doc.selling_cost_center - item.actual_qty = frappe.db.get_value('Bin', {'item_code': item.name, - 'warehouse': item.default_warehouse}, 'actual_qty') or 0 - item.serial_nos = get_serial_nos(item, pos_profile, doc.company) - item.batch_nos = frappe.db.sql_list("""select name from `tabBatch` where ifnull(expiry_date, '4000-10-10') > curdate() - and item = %(item_code)s""", {'item_code': item.item_code}) + cond = "item_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('item_groups')))) + item_groups = [d.item_group for d in pos_profile.get('item_groups')] - item_list.append(item) + return frappe.db.sql(""" + select + name, item_code, item_name, description, item_group, expense_account, has_batch_no, + has_serial_no, expense_account, selling_cost_center, stock_uom, image, + default_warehouse, is_stock_item + from + tabItem + where + disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond} + """.format(cond=cond), tuple(item_groups), as_dict=1) - return item_list +def get_customers_list(pos_profile): + cond = "1=1" + customer_groups = [] + if pos_profile.get('customer_groups'): + # Get customers based on the customer groups defined in the POS profile -def get_item_warehouse_for_company(company, warehouse): - if frappe.db.get_value('Warehouse', warehouse, 'company') != company: - warehouse = None - return warehouse + cond = "customer_group in (%s)"%(', '.join(['%s']*len(pos_profile.get('customer_groups')))) + customer_groups = [d.customer_group for d in pos_profile.get('customer_groups')] + + return frappe.db.sql(""" select name, customer_name, customer_group, + territory from tabCustomer where disabled = 0 + and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} + +def get_serial_no_data(pos_profile, company): + # get itemwise serial no data + # example {'Nokia Lumia 1020': {'SN0001': 'Pune'}} + # where Nokia Lumia 1020 is item code, SN0001 is serial no and Pune is warehouse -def get_serial_nos(item, pos_profile, company): cond = "1=1" if pos_profile.get('update_stock') and pos_profile.get('warehouse'): cond = "warehouse = '{0}'".format(pos_profile.get('warehouse')) - serial_nos = frappe.db.sql("""select name, warehouse from `tabSerial No` where {0} - and item_code = %(item_code)s and company = %(company)s - """.format(cond), {'item_code': item.item_code, 'company': company}, as_dict=1) + serial_nos = frappe.db.sql("""select name, warehouse, item_code from `tabSerial No` where {0} + and company = %(company)s """.format(cond), {'company': company}, as_dict=1) - serial_no_list = {} - for serial_no in serial_nos: - serial_no_list[serial_no.name] = serial_no.warehouse + itemwise_serial_no = {} + for sn in serial_nos: + if sn.item_code not in itemwise_serial_no: + itemwise_serial_no.setdefault(sn.item_code, {}) + itemwise_serial_no[sn.item_code][sn.name] = sn.warehouse - return serial_no_list + return itemwise_serial_no -def get_customers(pos_profile, doc, company_currency): - filters = {'disabled': 0} - customer_list = [] - customers = frappe.get_all("Customer", fields=["*"], filters = filters) +def get_batch_no_data(): + # get itemwise batch no data + # exmaple: {'LED-GRE': [Batch001, Batch002]} + # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse - for customer in customers: - customer_currency = get_party_account_currency('Customer', customer.name, doc.company) or doc.currency - if customer_currency == doc.currency or customer_currency == company_currency: - customer_list.append(customer) - return customer_list + itemwise_batch = {} + batches = frappe.db.sql("""select name, item from `tabBatch` + where ifnull(expiry_date, '4000-10-10') >= curdate()""", as_dict=1) -def get_pricing_rules(doc): + for batch in batches: + if batch.item not in itemwise_batch: + itemwise_batch.setdefault(batch.item, []) + itemwise_batch[batch.item].append(batch.name) + + return itemwise_batch + +def get_item_tax_data(): + # get default tax of an item + # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} + + itemwise_tax = {} + taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1) + + for tax in taxes: + if tax.parent not in itemwise_tax: + itemwise_tax.setdefault(tax.parent, {}) + itemwise_tax[tax.parent][tax.tax_type] = tax.tax_rate + + return itemwise_tax + +def get_price_list_data(selling_price_list): + itemwise_price_list = {} + price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate, + item_code from `tabItem Price` ip where price_list = %(price_list)s""", + {'price_list': selling_price_list}, as_dict=1) + + for item in price_lists: + itemwise_price_list[item.item_code] = item.price_list_rate + + return itemwise_price_list + +def get_bin_data(pos_profile): + itemwise_bin_data = {} + cond = "1=1" + if pos_profile.get('warehouse'): + cond = "warehouse = '{0}'".format(pos_profile.get('warehouse')) + + bin_data = frappe.db.sql(""" select item_code, warehouse, actual_qty from `tabBin` + where actual_qty > 0 and {cond}""".format(cond=cond), as_dict=1) + + for bins in bin_data: + if bins.item_code not in itemwise_bin_data: + itemwise_bin_data.setdefault(bins.item_code, {}) + itemwise_bin_data[bins.item_code][bins.warehouse] = bins.actual_qty + + return itemwise_bin_data + +def get_pricing_rule_data(doc): pricing_rules = "" if doc.ignore_pricing_rule == 0: pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2 diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 9b4306e68d..4cd66f532f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -20,6 +20,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte erpnext.queries.setup_queries(this.frm, "Warehouse", function() { return erpnext.queries.warehouse(me.frm.doc); }); + + if(this.frm.doc.__islocal && this.frm.doc.is_pos) { + //Load pos profile data on the invoice if the default value of Is POS is 1 + + me.frm.script_manager.trigger("is_pos"); + me.frm.refresh_fields(); + } }, refresh: function(doc, dt, dn) { diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 511eeaab9f..c4f275aba9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -7,6 +7,7 @@ import unittest, copy from frappe.utils import nowdate, add_days, flt, nowdate from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError @@ -467,7 +468,7 @@ class TestSalesInvoice(unittest.TestCase): def test_pos_gl_entry_with_perpetual_inventory(self): set_perpetual_inventory() - self.make_pos_profile() + make_pos_profile() self._insert_purchase_receipt() pos = copy.deepcopy(test_records[1]) @@ -486,7 +487,7 @@ class TestSalesInvoice(unittest.TestCase): def test_pos_change_amount(self): set_perpetual_inventory() - self.make_pos_profile() + make_pos_profile() self._insert_purchase_receipt() pos = copy.deepcopy(test_records[1]) @@ -508,7 +509,7 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory() - self.make_pos_profile() + make_pos_profile() self._insert_purchase_receipt() pos = copy.deepcopy(test_records[1]) @@ -572,26 +573,6 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") - def make_pos_profile(self): - pos_profile = frappe.get_doc({ - "company": "_Test Company", - "cost_center": "_Test Cost Center - _TC", - "currency": "INR", - "doctype": "POS Profile", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "income_account": "Sales - _TC", - "name": "_Test POS Profile", - "naming_series": "_T-POS Profile-", - "selling_price_list": "_Test Price List", - "territory": "_Test Territory", - "warehouse": "_Test Warehouse - _TC", - "write_off_account": "_Test Write Off - _TC", - "write_off_cost_center": "_Test Write Off Cost Center - _TC" - }) - - if not frappe.db.exists("POS Profile", "_Test POS Profile"): - pos_profile.insert() - def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self): set_perpetual_inventory() diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 32e9b3ab60..57ffcb1346 100644 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -25,7 +25,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.set_indicator(); this.onload(); this.make_menu_list(); - this.set_interval_for_si_sync(); this.si_docs = this.get_doc_from_localstorage(); }, @@ -73,8 +72,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.get_data_from_server(function(){ me.create_new(); }); - - this.check_internet_connection(); }, make_menu_list: function(){ @@ -204,13 +201,10 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ freeze: true, freeze_message: __("Master data syncing, it might take some time"), callback: function(r){ - window.items = r.message.items; - window.customers = r.message.customers; - window.pricing_rules = r.message.pricing_rules; - window.meta = r.message.meta; - window.print_template = r.message.print_template; - me.default_customer = r.message.default_customer || null; + me.init_master_data(r) localStorage.setItem('doc', JSON.stringify(r.message.doc)); + me.set_interval_for_si_sync(); + me.check_internet_connection(); if(callback){ callback(); } @@ -218,6 +212,22 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }) }, + init_master_data: function(r){ + var me = this; + this.meta = r.message.meta; + this.item_data = r.message.items; + this.customers = r.message.customers; + this.serial_no_data = r.message.serial_no_data; + this.batch_no_data = r.message.batch_no_data; + this.tax_data = r.message.tax_data; + this.price_list_data = r.message.price_list_data; + this.bin_data = r.message.bin_data; + this.pricing_rules = r.message.pricing_rules; + this.print_template = r.message.print_template; + this.pos_profile_data = r.message.pos_profile; + this.default_customer = r.message.default_customer || null; + }, + save_previous_entry : function(){ if(this.frm.doc.items.length > 0){ this.create_invoice() @@ -233,20 +243,19 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }, load_data: function(load_doc){ - this.items = window.items; - this.customers = window.customers; - this.pricing_rules = window.pricing_rules; + var me = this; + this.items = this.item_data; if(load_doc) { this.frm.doc = JSON.parse(localStorage.getItem('doc')); } - $.each(window.meta, function(i, data){ + $.each(this.meta, function(i, data){ frappe.meta.sync(data) }) this.print_template = frappe.render_template("print_template", - {content: window.print_template, title:"POS", + {content: this.print_template, title:"POS", base_url: frappe.urllib.get_base_url(), print_css: frappe.boot.print_css}) }, @@ -387,12 +396,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ var $wrap = me.wrapper.find(".item-list"); me.wrapper.find(".item-list").empty(); - if (this.items) { + if (this.items.length > 0) { $.each(this.items, function(index, obj) { if(index < 30){ $(frappe.render_template("pos_item", { item_code: obj.name, - item_price: format_currency(obj.price_list_rate, me.frm.doc.currency), + item_price: format_currency(me.price_list_data[obj.name], me.frm.doc.currency), item_name: obj.name===obj.item_name ? "" : obj.item_name, item_image: obj.image ? "url('" + obj.image + "')" : null, color: frappe.get_palette(obj.item_name), @@ -400,6 +409,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ })).tooltip().appendTo($wrap); } }); + } else { + $("

Searching record not found.

").appendTo($wrap) } if(this.items.length == 1 @@ -426,7 +437,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.item_batch_no = {}; if(item_code){ - return $.grep(window.items, function(item){ + return $.grep(this.item_data, function(item){ if(item.item_code == item_code ){ return true } @@ -439,14 +450,15 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ search_status = true if(key){ - return $.grep(window.items, function(item){ + return $.grep(this.item_data, function(item){ if(search_status){ - if(in_list(item.batch_nos, me.search.$input.val())){ + if(in_list(me.batch_no_data[item.item_code], me.search.$input.val())){ search_status = false; return me.item_batch_no[item.item_code] = me.search.$input.val() - } else if(in_list(Object.keys(item.serial_nos), me.search.$input.val())) { + } else if( me.serial_no_data[item.item_code] + && in_list(Object.keys(me.serial_no_data[item.item_code]), me.search.$input.val())) { search_status = false; - me.item_serial_no[item.item_code] = [me.search.$input.val(), item.serial_nos[me.search.$input.val()]] + me.item_serial_no[item.item_code] = [me.search.$input.val(), me.serial_no_data[item.item_code][me.search.$input.val()]] return true } else if(item.barcode == me.search.$input.val()) { search_status = false; @@ -458,7 +470,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ } }) }else{ - return window.items; + return this.item_data; } }, @@ -611,18 +623,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.child.description = this.items[0].description; this.child.qty = 1; this.child.item_group = this.items[0].item_group; - this.child.cost_center = this.items[0].cost_center; - this.child.income_account = this.items[0].income_account; + this.child.cost_center = this.pos_profile_data['cost_center'] || this.items[0].cost_center; + this.child.income_account = this.pos_profile_data['income_account'] || this.items[0].income_account; this.child.warehouse = (this.item_serial_no[this.child.item_code] - ? this.item_serial_no[this.child.item_code][1] : this.items[0].default_warehouse); - this.child.price_list_rate = flt(this.items[0].price_list_rate, 9) / flt(this.frm.doc.conversion_rate, 9); - this.child.rate = flt(this.items[0].price_list_rate, 9) / flt(this.frm.doc.conversion_rate, 9); - this.child.actual_qty = this.items[0].actual_qty; + ? this.item_serial_no[this.child.item_code][1] : (this.pos_profile_data['warehouse'] || this.items[0].default_warehouse) ); + this.child.price_list_rate = flt(this.price_list_data[this.child.item_code], 9) / flt(this.frm.doc.conversion_rate, 9); + this.child.rate = flt(this.price_list_data[this.child.item_code], 9) / flt(this.frm.doc.conversion_rate, 9); + this.child.actual_qty = me.get_actual_qty(this.items[0]); this.child.amount = flt(this.child.qty) * flt(this.child.rate); this.child.batch_no = this.item_batch_no[this.child.item_code]; this.child.serial_no = (this.item_serial_no[this.child.item_code] ? this.item_serial_no[this.child.item_code][0] : ''); - this.child.item_tax_rate = this.items[0].taxes; + this.child.item_tax_rate = JSON.stringify(this.tax_data[this.child.item_code]); }, update_paid_amount_status: function(update_paid_amount){ @@ -668,7 +680,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ item_code: d.item_code, item_name: (d.item_name===d.item_code || !d.item_name) ? "" : ("
" + d.item_name), qty: d.qty, - actual_qty: d.actual_qty, + actual_qty: me.actual_qty, projected_qty: d.projected_qty, rate: format_number(d.rate, me.frm.doc.currency), amount: format_currency(d.amount, me.frm.doc.currency) @@ -1064,8 +1076,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }, validate_warehouse: function(){ - if(!this.items[0].default_warehouse){ + if(this.items[0].is_stock_item && !this.items[0].default_warehouse && !this.pos_profile_data['warehouse']){ frappe.throw(__("Default warehouse is required for selected item")) } + }, + + get_actual_qty: function(item) { + this.actual_qty = 0.0; + var warehouse = this.pos_profile_data['warehouse'] || item.default_warehouse; + if(warehouse && this.bin_data[item.item_code]) { + this.actual_qty = this.bin_data[item.item_code][warehouse] || 0; + } + + return this.actual_qty } }) \ No newline at end of file diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 8963bb4e50..5ff8e3e228 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -14,10 +14,10 @@ def execute(filters=None): liability = get_data(filters.company, "Liability", "Credit", period_list, only_current_fiscal_year=False) equity = get_data(filters.company, "Equity", "Credit", period_list, only_current_fiscal_year=False) - provisional_profit_loss,total_credit = get_provisional_profit_loss(asset, liability, equity, + provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity, period_list, filters.company) - message,opening_balance = check_opening_balance(asset, liability, equity) + message, opening_balance = check_opening_balance(asset, liability, equity) data = [] data.extend(asset or []) @@ -32,7 +32,9 @@ def execute(filters=None): } for period in period_list: unclosed[period.key] = opening_balance - provisional_profit_loss[period.key] = provisional_profit_loss[period.key] - opening_balance + if provisional_profit_loss: + provisional_profit_loss[period.key] = provisional_profit_loss[period.key] - opening_balance + unclosed["total"]=opening_balance data.append(unclosed) @@ -48,15 +50,11 @@ def execute(filters=None): return columns, data, message, chart def get_provisional_profit_loss(asset, liability, equity, period_list, company): + provisional_profit_loss = {} + total_row = {} if asset and (liability or equity): total = total_row_total=0 currency = frappe.db.get_value("Company", company, "default_currency") - provisional_profit_loss = { - "account_name": "'" + _("Provisional Profit / Loss (Credit)") + "'", - "account": None, - "warn_if_negative": True, - "currency": currency - } total_row = { "account_name": "'" + _("Total (Credit)") + "'", "account": None, @@ -85,9 +83,14 @@ def get_provisional_profit_loss(asset, liability, equity, period_list, company): total_row["total"] = total_row_total if has_value: - return provisional_profit_loss, total_row - return None,total_row - return None, None + provisional_profit_loss.update({ + "account_name": "'" + _("Provisional Profit / Loss (Credit)") + "'", + "account": None, + "warn_if_negative": True, + "currency": currency + }) + + return provisional_profit_loss, total_row def check_opening_balance(asset, liability, equity): # Check if previous year balance sheet closed diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html index dd1609a00c..23d2a31fff 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html @@ -8,7 +8,7 @@ {%= __("Posting Date") %} - {%= __("Journal Entry") %} + {%= __("Payment Entry") %} {%= __("Reference") %} {%= __("Debit") %} {%= __("Credit") %} @@ -19,10 +19,10 @@ {% if (data[i]["posting_date"]) { %} {%= dateutil.str_to_user(data[i]["posting_date"]) %} - {%= data[i]["journal_entry"] %} + {%= data[i]["payment_entry"] %} {%= __("Against") %}: {%= data[i]["against_account"] %} - {% if (data[i]["reference"]) { %} -
{%= __("Reference") %}: {%= data[i]["reference"] %} + {% if (data[i]["reference_no"]) { %} +
{%= __("Reference") %}: {%= data[i]["reference_no"] %} {% if (data[i]["ref_date"]) { %}
{%= __("Reference Date") %}: {%= dateutil.str_to_user(data[i]["ref_date"]) %} {% } %} @@ -38,7 +38,7 @@ - {%= data[i]["journal_entry"] %} + {%= data[i]["payment_entry"] %} {%= format_currency(data[i]["debit"]) %} {%= format_currency(data[i]["credit"]) %} diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index 8ed338efe1..95b7ff7545 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -129,7 +129,7 @@ def get_entries(filters): reference_no, reference_date as ref_date, if(paid_to=%(account)s, received_amount, 0) as debit, if(paid_from=%(account)s, paid_amount, 0) as credit, - posting_date, party as against_account, clearance_date, + posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency from `tabPayment Entry` where diff --git a/erpnext/accounts/report/cash_flow/cash_flow.html b/erpnext/accounts/report/cash_flow/cash_flow.html new file mode 100644 index 0000000000..40ba20c4ac --- /dev/null +++ b/erpnext/accounts/report/cash_flow/cash_flow.html @@ -0,0 +1 @@ +{% include "accounts/report/financial_statements.html" %} \ No newline at end of file diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.html b/erpnext/accounts/report/profitability_analysis/profitability_analysis.html new file mode 100644 index 0000000000..40ba20c4ac --- /dev/null +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.html @@ -0,0 +1 @@ +{% include "accounts/report/financial_statements.html" %} \ No newline at end of file diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js index 4d2efad8b8..7ccec30bde 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js @@ -50,6 +50,19 @@ frappe.query_reports["Trial Balance for Party"] = { "options": ["Customer", "Supplier"], "default": "Customer" }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "get_options": function() { + var party_type = frappe.query_report_filters_by_name.party_type.get_value(); + var party = frappe.query_report_filters_by_name.party.get_value(); + if(party && !party_type) { + frappe.throw(__("Please select Party Type first")); + } + return party_type; + } + }, { "fieldname": "show_zero_values", "label": __("Show zero values"), diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index 58222ac0d5..6480623ffa 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -20,13 +20,23 @@ def execute(filters=None): def get_data(filters, show_party_name): party_name_field = "customer_name" if filters.get("party_type")=="Customer" else "supplier_name" - parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], order_by="name") + party_filters = {"name": filters.get("party")} if filters.get("party") else {} + parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], + filters = party_filters, order_by="name") company_currency = frappe.db.get_value("Company", filters.company, "default_currency") opening_balances = get_opening_balances(filters) balances_within_period = get_balances_within_period(filters) data = [] - total_debit, total_credit = 0, 0 + # total_debit, total_credit = 0, 0 + total_row = frappe._dict({ + "opening_debit": 0, + "opening_credit": 0, + "debit": 0, + "credit": 0, + "closing_debit": 0, + "closing_credit": 0 + }) for party in parties: row = { "party": party.name } if show_party_name: @@ -45,11 +55,7 @@ def get_data(filters, show_party_name): "debit": debit, "credit": credit }) - - # totals - total_debit += debit - total_credit += credit - + # closing closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit) row.update({ @@ -57,6 +63,10 @@ def get_data(filters, show_party_name): "closing_credit": closing_credit }) + # totals + for col in total_row: + total_row[col] += row.get(col) + row.update({ "currency": company_currency }) @@ -69,13 +79,12 @@ def get_data(filters, show_party_name): data.append(row) # Add total row - if total_debit or total_credit: - data.append({ - "party": "'" + _("Totals") + "'", - "debit": total_debit, - "credit": total_credit, - "currency": company_currency - }) + + total_row.update({ + "party": "'" + _("Totals") + "'", + "currency": company_currency + }) + data.append(total_row) return data diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 419d83f645..b9c503a7b0 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -371,6 +371,12 @@ def get_data(): "doctype": "GL Entry", "is_query_report": True, }, + { + "type": "report", + "name": "Profitability Analysis", + "doctype": "GL Entry", + "is_query_report": True, + }, { "type": "report", "name": "Payment Period Based On Invoice Date", diff --git a/erpnext/docs/assets/img/buying/add_taxes_to_doc.png b/erpnext/docs/assets/img/buying/add_taxes_to_doc.png new file mode 100644 index 0000000000..3e1480de72 Binary files /dev/null and b/erpnext/docs/assets/img/buying/add_taxes_to_doc.png differ diff --git a/erpnext/docs/assets/img/buying/buying_flow.png b/erpnext/docs/assets/img/buying/buying_flow.png new file mode 100644 index 0000000000..7b3f256414 Binary files /dev/null and b/erpnext/docs/assets/img/buying/buying_flow.png differ diff --git a/erpnext/docs/assets/img/buying/show_tax_breakup.png b/erpnext/docs/assets/img/buying/show_tax_breakup.png new file mode 100644 index 0000000000..7348d51b5b Binary files /dev/null and b/erpnext/docs/assets/img/buying/show_tax_breakup.png differ diff --git a/erpnext/docs/user/manual/en/buying/purchase-order.md b/erpnext/docs/user/manual/en/buying/purchase-order.md index e7f24290fa..89bb6d9d5f 100644 --- a/erpnext/docs/user/manual/en/buying/purchase-order.md +++ b/erpnext/docs/user/manual/en/buying/purchase-order.md @@ -7,7 +7,7 @@ Supplier Quotation. #### Purchase Order Flow Chart -![Purchase Order]({{docs_base_url}}/assets/old_images/erpnext/purchase-order-f.jpg) +![Purchase Order]({{docs_base_url}}/assets/img/buying/buying_flow.png) In ERPNext, you can also make a Purchase Order directly by going to: @@ -39,6 +39,12 @@ which you collect from your Customer. In many regions, what you pay to your government is only the difference between what you collect from your Customer and what you pay to your Supplier. This is called Value Added Tax (VAT). +#### Add Taxes in Purchase Order +Purchase Order + +#### Show Tax break-up +Purchase Order + For example you buy Items worth X and sell them for 1.3X. So your Customer pays 1.3 times the tax you pay your Supplier. Since you have already paid tax to your Supplier for X, what you owe your government is only the tax on 0.3X. @@ -49,7 +55,7 @@ Ideally you must create two Accounts for each type of VAT you pay and collect, effect. Please contact your accountant if you need more help or post a query on our forums! - + #### Purchase UOM and Stock UOM Conversion @@ -82,5 +88,5 @@ __Step 5:__ Notice that the stock quantity will be updated accordingly. __Step 6:__ Save and Submit the Form. - + {next} diff --git a/erpnext/docs/user/manual/en/buying/supplier-quotation.md b/erpnext/docs/user/manual/en/buying/supplier-quotation.md index 0f1d51aae4..868a2977e9 100644 --- a/erpnext/docs/user/manual/en/buying/supplier-quotation.md +++ b/erpnext/docs/user/manual/en/buying/supplier-quotation.md @@ -8,7 +8,7 @@ You can make a supplier quotation from a Material Request #### Supplier Quotation Flow-Chart -![Supplier Quotation]({{docs_base_url}}/assets/old_images/erpnext/supplier-quotation-f.jpg) +![Supplier Quotation]({{docs_base_url}}/assets/img/buying/buying_flow.png) You can also make a Supplier Quotation directly from: @@ -23,11 +23,22 @@ usually send out a message (Request for Quote) to various Suppliers. In many cases, especially if you have centralized buying, you may want to record all the quotes so that - * You can easily compare prices in the future + * You can easily compare prices in the future * Audit whether all Suppliers were given the opportunity to quote. Supplier Quotations are not necessary for most small businesses. Always evaluate the cost of collecting information to the value it really provides! You could only do this for high value items. +#### Taxes +If your Supplier is going to charge you additional taxes or charge like a shipping or insurance charge, you can add it here. It will help you to accurately track your costs. Also, if some of these charges add to the value of the product you will have to mention them in the Taxes table. You can also use templates for your taxes. For more information on setting up your taxes see the Purchase Taxes and Charges Template. + +You can select relevant tax by going to "Taxes and Charges" section and adding an entry to the table as shown below, + +Supplier Quotation + +Besides, in case of multiple items you can keep track of taxes on each by clicking "Show tax break-up" + +Supplier Quotation + {next} diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py index aa9e78c399..a3d0f1b406 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -486,7 +486,9 @@ class ProductionPlanningTool(Document): "qty": requested_qty, "schedule_date": add_days(nowdate(), cint(item_wrapper.lead_time_days)), "warehouse": self.purchase_request_for_warehouse, - "sales_order": sales_order if sales_order!="No Sales Order" else None + "sales_order": sales_order if sales_order!="No Sales Order" else None, + "project": frappe.db.get_value("Sales Order", sales_order, "project") \ + if sales_order!="No Sales Order" else None }) material_request.flags.ignore_permissions = 1 diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index 153d7e5623..ba8e30cba0 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -1,17 +1,19 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - - -//--------- ONLOAD ------------- -cur_frm.cscript.onload = function(doc, cdt, cdn) { - frappe.call({ - type:"GET", - method:"erpnext.manufacturing.doctype.workstation.workstation.get_default_holiday_list", - callback: function(r) { - if(!r.exe && r.message){ - cur_frm.set_value("holiday_list", r.message); - } +frappe.ui.form.on("Workstation", { + onload: function(frm) { + if(frm.is_new()) + { + frappe.call({ + type:"GET", + method:"erpnext.manufacturing.doctype.workstation.workstation.get_default_holiday_list", + callback: function(r) { + if(!r.exe && r.message){ + cur_frm.set_value("holiday_list", r.message); + } + } + }) } - }) -} + } +}) \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5f8531d1a3..30e8e038f7 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -349,3 +349,4 @@ erpnext.patches.v7_0.set_base_amount_in_invoice_payment_table erpnext.patches.v7_1.update_invoice_status erpnext.patches.v7_0.po_status_issue_for_pr_return erpnext.patches.v7_1.update_missing_salary_component_type +erpnext.patches.v7_0.update_autoname_field \ No newline at end of file diff --git a/erpnext/patches/v7_0/fix_duplicate_icons.py b/erpnext/patches/v7_0/fix_duplicate_icons.py index 3e762f79b4..f6d227d304 100644 --- a/erpnext/patches/v7_0/fix_duplicate_icons.py +++ b/erpnext/patches/v7_0/fix_duplicate_icons.py @@ -6,6 +6,8 @@ from erpnext.patches.v7_0.migrate_schools_to_erpnext import reload_doctypes_for_ def execute(): '''hide new style icons if old ones are set''' + frappe.reload_doc('desk', 'doctype', 'desktop_icon') + reload_doctypes_for_schools_icons() sync_desktop_icons() diff --git a/erpnext/patches/v7_0/update_autoname_field.py b/erpnext/patches/v7_0/update_autoname_field.py new file mode 100644 index 0000000000..bfa9b281df --- /dev/null +++ b/erpnext/patches/v7_0/update_autoname_field.py @@ -0,0 +1,14 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + doctypes = frappe.db.sql(""" select name, autoname from `tabDocType` + where autoname like 'field:%' and allow_rename = 1""", as_dict=1) + + for doctype in doctypes: + fieldname = doctype.autoname.split(":")[1] + if fieldname: + frappe.db.sql(""" update `tab%s` set %s = name """%(doctype.name, fieldname)) \ No newline at end of file diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index a04c652104..59fdf703a3 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -66,27 +66,41 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }); } + if(this.frm.fields_dict["packed_items"].grid.get_field('batch_no')) { + this.frm.set_query("batch_no", "packed_items", function(doc, cdt, cdn) { + return me.set_query_for_batch(doc, cdt, cdn) + }); + } + if(this.frm.fields_dict["items"].grid.get_field('batch_no')) { this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - if(!item.item_code) { - frappe.throw(__("Please enter Item Code to get batch no")); - } else { - filters = { - 'item_code': item.item_code, - 'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(), - } - if(item.warehouse) filters["warehouse"] = item.warehouse - - return { - query : "erpnext.controllers.queries.get_batch_no", - filters: filters - } - } + return me.set_query_for_batch(doc, cdt, cdn) }); } }, + set_query_for_batch: function(doc, cdt, cdn) { + // Show item's batches in the dropdown of batch no + + var me = this; + var item = frappe.get_doc(cdt, cdn); + + if(!item.item_code) { + frappe.throw(__("Please enter Item Code to get batch no")); + } else { + filters = { + 'item_code': item.item_code, + 'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(), + } + if(item.warehouse) filters["warehouse"] = item.warehouse + + return { + query : "erpnext.controllers.queries.get_batch_no", + filters: filters + } + } + }, + refresh: function() { this._super(); this.frm.toggle_display("customer_name", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 06df636cd7..4fb5f5d639 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -372,6 +372,7 @@ def raise_production_orders(material_request): mr= frappe.get_doc("Material Request", material_request) errors =[] production_orders = [] + default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") for d in mr.items: if (d.qty - d.ordered_qty) >0: if frappe.db.get_value("BOM", {"item": d.item_code, "is_default": 1}): @@ -379,6 +380,7 @@ def raise_production_orders(material_request): prod_order.production_item = d.item_code prod_order.qty = d.qty - d.ordered_qty prod_order.fg_warehouse = d.warehouse + prod_order.wip_warehouse = default_wip_warehouse prod_order.description = d.description prod_order.stock_uom = d.uom prod_order.expected_delivery_date = d.schedule_date diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 2078eebded..09affe0494 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -16,7 +16,7 @@ class PackedItem(Document): def get_product_bundle_items(item_code): return frappe.db.sql("""select t1.item_code, t1.qty, t1.uom, t1.description from `tabProduct Bundle Item` t1, `tabProduct Bundle` t2 - where t2.new_item_code=%s and t1.parent = t2.name""", item_code, as_dict=1) + where t2.new_item_code=%s and t1.parent = t2.name order by t1.idx""", item_code, as_dict=1) def get_packing_item_details(item): return frappe.db.sql("""select item_name, description, stock_uom from `tabItem`