From 6087fe178e478ef4bcc5596eb15641b4cf02cb01 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 9 Apr 2016 14:31:09 +0530 Subject: [PATCH] [Enhancement] POS --- .../mode_of_payment/mode_of_payment.json | 37 +- erpnext/accounts/doctype/payments/__init__.py | 0 .../accounts/doctype/payments/payments.json | 191 ++++ erpnext/accounts/doctype/payments/payments.py | 10 + .../doctype/pos_profile/pos_profile.json | 272 +++++- .../doctype/pos_profile/pos_profile.py | 4 + .../purchase_invoice/purchase_invoice.js | 2 +- erpnext/accounts/doctype/sales_invoice/pos.py | 218 ++++- .../doctype/sales_invoice/sales_invoice.js | 49 - .../doctype/sales_invoice/sales_invoice.json | 224 ++++- .../doctype/sales_invoice/sales_invoice.py | 73 +- .../sales_invoice/test_sales_invoice.py | 41 +- erpnext/accounts/page/pos/pos.js | 877 +++++++++++++++++- erpnext/accounts/party_status.py | 1 + erpnext/controllers/accounts_controller.py | 16 +- erpnext/controllers/taxes_and_totals.py | 30 +- erpnext/public/build.json | 5 + erpnext/public/css/erpnext.css | 46 + erpnext/public/js/controllers/payments.js | 158 ++++ .../public/js/controllers/taxes_and_totals.js | 94 +- erpnext/public/js/controllers/transaction.js | 4 +- .../public/js/payment/payment_details.html | 4 + erpnext/public/js/payment/pos_payment.html | 40 + erpnext/public/js/pos/pos.html | 33 +- erpnext/public/js/pos/pos_bill_item.html | 3 +- erpnext/public/js/pos/pos_invoice_list.html | 5 + erpnext/public/js/pos/pos_print.html | 25 + erpnext/selling/sales_common.js | 23 +- 28 files changed, 2197 insertions(+), 288 deletions(-) create mode 100644 erpnext/accounts/doctype/payments/__init__.py create mode 100644 erpnext/accounts/doctype/payments/payments.json create mode 100644 erpnext/accounts/doctype/payments/payments.py create mode 100644 erpnext/public/js/controllers/payments.js create mode 100644 erpnext/public/js/payment/payment_details.html create mode 100644 erpnext/public/js/payment/pos_payment.html create mode 100644 erpnext/public/js/pos/pos_invoice_list.html create mode 100644 erpnext/public/js/pos/pos_print.html diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json index 6befedcfd8..26519ac5ff 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json @@ -17,6 +17,7 @@ "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Mode of Payment", @@ -26,6 +27,7 @@ "oldfieldtype": "Data", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -33,6 +35,32 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Type", + "length": 0, + "no_copy": 0, + "options": "Cash\nBank\nGeneral", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -41,6 +69,7 @@ "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Accounts", @@ -50,6 +79,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -68,7 +98,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2015-11-16 06:29:50.335559", + "modified": "2016-04-26 11:48:17.411796", "modified_by": "Administrator", "module": "Accounts", "name": "Mode of Payment", @@ -115,6 +145,9 @@ "write": 0 } ], + "quick_entry": 1, "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "sort_order": "ASC", + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payments/__init__.py b/erpnext/accounts/doctype/payments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/payments/payments.json b/erpnext/accounts/doctype/payments/payments.json new file mode 100644 index 0000000000..d52fb1519c --- /dev/null +++ b/erpnext/accounts/doctype/payments/payments.json @@ -0,0 +1,191 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-05-08 23:49:38.842621", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Mode of Payment", + "length": 0, + "no_copy": 0, + "options": "Mode of Payment", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "0.0", + "depends_on": "eval:parent.doctype == 'Sales Invoice'", + "fieldname": "amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Amount", + "length": 0, + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_3", + "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, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "type", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Type", + "length": 0, + "no_copy": 0, + "options": "mode_of_payment.type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "base_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Base Amount (Company Currency)", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-05-09 00:14:18.975568", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payments", + "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/payments/payments.py b/erpnext/accounts/doctype/payments/payments.py new file mode 100644 index 0000000000..15cf0b26a7 --- /dev/null +++ b/erpnext/accounts/doctype/payments/payments.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 Payments(Document): + pass diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 6d395affd6..8f25ba90b3 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -3,6 +3,7 @@ "allow_import": 0, "allow_rename": 0, "autoname": "hash", + "beta": 0, "creation": "2013-05-24 12:15:51", "custom": 0, "docstatus": 0, @@ -16,6 +17,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Applicable for User", @@ -26,6 +28,7 @@ "options": "User", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -41,6 +44,7 @@ "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Series", @@ -51,6 +55,7 @@ "options": "[Select]", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -58,31 +63,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Warehouse", - "length": 0, - "no_copy": 0, - "oldfieldname": "warehouse", - "oldfieldtype": "Link", - "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -93,6 +73,7 @@ "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Update Stock", @@ -100,6 +81,60 @@ "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "update_stock", + "fieldname": "warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Warehouse", + "length": 0, + "no_copy": 0, + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Ignore Pricing Rule", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -115,6 +150,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -122,6 +158,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -137,6 +174,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Customer", @@ -147,6 +185,7 @@ "options": "Customer", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -162,6 +201,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Company", @@ -172,6 +212,7 @@ "options": "Company", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -187,6 +228,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Currency", @@ -197,6 +239,7 @@ "options": "Currency", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -208,19 +251,70 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "mode_of_payment", - "fieldtype": "Link", + "fieldname": "allow_partial_payment", + "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Mode of Payment", + "label": "Allow Partial Payment", "length": 0, "no_copy": 0, - "options": "Mode of Payment", "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "section_break_11", + "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, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "payments", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Payments", + "length": 0, + "no_copy": 0, + "options": "Payments", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -236,6 +330,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -243,6 +338,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -258,6 +354,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Print Format", @@ -267,6 +364,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -282,6 +380,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Letter Head", @@ -292,6 +391,7 @@ "options": "Letter Head", "permlevel": 0, "print_hide": 1, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -307,6 +407,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Print Heading", @@ -317,6 +418,7 @@ "options": "Print Heading", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -332,6 +434,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Terms and Conditions", @@ -342,6 +445,7 @@ "options": "Terms and Conditions", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -357,6 +461,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -364,6 +469,33 @@ "oldfieldtype": "Column Break", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 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, "report_hide": 0, "reqd": 0, @@ -380,6 +512,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Territory", @@ -390,6 +523,7 @@ "options": "Territory", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -405,6 +539,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Price List", @@ -415,6 +550,60 @@ "options": "Price List", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "apply_discount", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Apply Discount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "Grand Total", + "depends_on": "apply_discount", + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Apply Discount On", + "length": 0, + "no_copy": 0, + "options": "Grand Total\nNet Total", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -430,6 +619,7 @@ "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -437,6 +627,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -448,10 +639,12 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "depends_on": "", "fieldname": "write_off_account", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Write Off Account", @@ -461,6 +654,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -476,6 +670,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Write Off Cost Center", @@ -485,6 +680,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -500,6 +696,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Taxes and Charges", @@ -510,6 +707,7 @@ "options": "Sales Taxes and Charges Template", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -525,6 +723,7 @@ "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "length": 0, @@ -532,6 +731,7 @@ "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -547,6 +747,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Cash/Bank Account", @@ -557,6 +758,7 @@ "options": "Account", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -572,6 +774,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Income Account", @@ -582,6 +785,7 @@ "options": "Account", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -598,6 +802,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Expense Account", @@ -606,6 +811,7 @@ "options": "Account", "permlevel": 0, "print_hide": 1, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -621,6 +827,7 @@ "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Cost Center", @@ -631,6 +838,7 @@ "options": "Cost Center", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -649,7 +857,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2015-11-16 06:29:51.741253", + "modified": "2016-05-09 00:00:30.610878", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", @@ -696,9 +904,11 @@ "write": 0 } ], + "quick_entry": 1, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "user" + "title_field": "user", + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 98a8509792..634d63c190 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import msgprint, _ from frappe.utils import cint +from erpnext.accounts.doctype.sales_invoice.sales_invoice import set_account_for_mode_of_payment from frappe.model.document import Document @@ -36,6 +37,9 @@ class POSProfile(Document): "company": self.company, "name": link_dn}): frappe.throw(_("{0} does not belong to Company {1}").format(link_dn, self.company)) + def before_save(self): + set_account_for_mode_of_payment(self) + def on_update(self): self.set_defaults() diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c534415d72..923514da5b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -129,7 +129,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ paid_amount: function() { this.set_in_company_currency(this.frm.doc, ["paid_amount"]); - this.write_off_outstanding_amount(); + this.write_off_amount(); this.frm.refresh_fields(); }, diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 62a9f526e6..0cf17da1c9 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -2,50 +2,186 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, json +from frappe import _ +from frappe.utils import nowdate +from erpnext.setup.utils import get_exchange_rate +from erpnext.stock.get_item_details import get_pos_profile +from erpnext.controllers.accounts_controller import get_taxes_and_charges @frappe.whitelist() -def get_items(price_list, sales_or_purchase, item=None): - condition = "" - order_by = "" - args = {"price_list": price_list} +def get_pos_data(): + doc = frappe.new_doc('Sales Invoice') + doc.update_stock = 1; + doc.is_pos = 1; + pos_profile = get_pos_profile(doc.company) - if item: - # search serial no - item_code = frappe.db.sql("""select name as serial_no, item_code - from `tabSerial No` where name=%s""", (item), as_dict=1) - if item_code: - item_code[0]["name"] = item_code[0]["item_code"] - return item_code + if not pos_profile: + frappe.throw(_("Create pos profile first")) - # search barcode - item_code = frappe.db.sql("""select name, item_code from `tabItem` - where barcode=%s""", - (item), as_dict=1) - if item_code: - item_code[0]["barcode"] = item - return item_code + pos_profile = frappe.get_doc('POS Profile', pos_profile.name) + update_pos_profile_data(doc, pos_profile) + update_multi_mode_option(doc, pos_profile) - condition += " and ((CONCAT(i.name, i.item_name) like %(name)s) or (i.variant_of like %(name)s) or (i.item_group like %(name)s))" - order_by = """if(locate(%(_name)s, i.name), locate(%(_name)s, i.name), 99999), - if(locate(%(_name)s, i.item_name), locate(%(_name)s, i.item_name), 99999), - if(locate(%(_name)s, i.variant_of), locate(%(_name)s, i.variant_of), 99999), - if(locate(%(_name)s, i.item_group), locate(%(_name)s, i.item_group), 99999),""" - args["name"] = "%%%s%%" % frappe.db.escape(item) - args["_name"] = item.replace("%", "") + return { + 'doc': doc, + 'items': get_items(doc, pos_profile), + 'customers': get_customers(pos_profile), + 'pricing_rules': get_pricing_rules(doc), + 'mode_of_payment': get_mode_of_payment(doc), + 'print_template': frappe.db.get_value('Print Format', pos_profile.print_format, 'html') or '', + 'meta': { + 'invoice': frappe.get_meta('Sales Invoice'), + 'items': frappe.get_meta('Sales Invoice Item'), + 'taxes': frappe.get_meta('Sales Taxes and Charges') + } + } - # locate function is used to sort by closest match from the beginning of the value - return frappe.db.sql("""select i.name, i.item_name, i.image, - item_det.price_list_rate, item_det.currency - from `tabItem` i LEFT JOIN - (select item_code, price_list_rate, currency from - `tabItem Price` where price_list=%(price_list)s) item_det - ON - (item_det.item_code=i.name or item_det.item_code=i.variant_of) - where - i.has_variants = 0 and - {condition} - order by - {order_by} - i.name - limit 24""".format(condition=condition, order_by=order_by), args, as_dict=1) +def update_pos_profile_data(doc, pos_profile): + company_data = frappe.db.get_value('Company', doc.company, '*', as_dict=1) + + doc.taxes_and_charges = pos_profile.taxes_and_charges + if doc.taxes_and_charges: + update_tax_table(doc) + + doc.currency = pos_profile.currency or company_data.default_currency + doc.conversion_rate = 1.0 + if doc.currency != company_data.default_currency: + doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency) + doc.selling_price_list = pos_profile.selling_price_list or frappe.db.get_value('Selling Settings', None, 'selling_price_list') + doc.naming_series = pos_profile.naming_series or 'SINV-' + doc.letter_head = pos_profile.letter_head or company_data.default_letter_head + doc.ignore_pricing_rule = pos_profile.ignore_pricing_rule + doc.apply_discount_on = pos_profile.apply_discount_on + doc.customer_group = pos_profile.customer_group or get_root('Customer Group') + doc.territory = pos_profile.territory or get_root('Territory') + +def get_root(table): + root = frappe.db.sql(""" select name from `tab%(table)s` having + min(lft)"""%{'table': table}, as_dict=1) + + return root[0].name + +def update_multi_mode_option(doc, pos_profile): + from frappe.model import default_fields + + for payment_mode in pos_profile.payments: + payment_mode = payment_mode.as_dict() + + for fieldname in default_fields: + if fieldname in payment_mode: + del payment_mode[fieldname] + + doc.append('payments', payment_mode) + +def update_tax_table(doc): + taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges) + 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}): + 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")))) + + 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.warehouse or item.default_warehouse or None + item.expense_account = pos_profile.expense_account or item.expense_account + item.income_account = pos_profile.income_account or item_doc.income_account + item.cost_center = pos_profile.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 = frappe.db.sql_list("""select name from `tabSerial No` where warehouse= %(warehouse)s + and item_code = %(item_code)s""", {'warehouse': item.default_warehouse, 'item_code': item.item_code}) + item_list.append(item) + + return item_list + +def get_customers(pos_profile): + filters = {'disabled': 0} + if pos_profile.customer: + filters.update({'name': pos_profile.customer}) + + return frappe.get_all("Customer", fields=["*"], filters = filters) + +def get_pricing_rules(doc): + if doc.ignore_pricing_rule == 0: + return frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2 and disable = 0 + and selling = 1 and ifnull(company, '') in (%(company)s, '') and + ifnull(for_price_list, '') in (%(price_list)s, '') and %(date)s between + ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31') order by priority desc, name desc""", + {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1) + +def get_mode_of_payment(doc): + return frappe.get_all('Mode of Payment Account', fields = ['distinct parent'], filters={'company': doc.company}) + +@frappe.whitelist() +def make_invoice(doc_list): + if isinstance(doc_list, basestring): + doc_list = json.loads(doc_list) + + name_list = [] + + for docs in doc_list: + for name, doc in docs.items(): + validate_customer(doc) + validate_item(doc) + si_doc = frappe.new_doc('Sales Invoice') + si_doc.offline_pos_name = name + si_doc.update(doc) + submit_invoice(si_doc) + name_list.append(name) + + return name_list + +def validate_customer(doc): + if not frappe.db.get_value('Customer', doc.get('customer')): + customer_doc = frappe.new_doc('Customer') + customer_doc.customer_name = doc.get('customer') + customer_doc.customer_type = 'Company' + customer_doc.customer_group = doc.get('customer_group') + customer_doc.territory = doc.get('territory') + customer_doc.save(ignore_permissions = True) + frappe.db.commit() + doc['customer'] = customer_doc.name + + return doc + +def validate_item(doc): + for item in doc.get('items'): + if not frappe.db.exists('Item', item.get('item_code')): + item_doc = frappe.new_doc('Item') + item_doc.name = item.get('item_code') + item_doc.item_code = item.get('item_code') + item_doc.item_name = item.get('item_name') + item_doc.description = item.get('description') + item_doc.default_warehouse = item.get('warehouse') + item_doc.stock_uom = item.get('stock_uom') + item_doc.item_group = item.get('item_group') + item_doc.save(ignore_permissions=True) + frappe.db.commit() + +def submit_invoice(si_doc): + try: + si_doc.insert() + si_doc.submit() + except Exception, e: + if frappe.message_log: frappe.message_log.pop() + frappe.db.rollback() + save_invoice(e, si_doc) + +def save_invoice(e, si_doc): + si_doc.docstatus = 0 + si_doc.name = '' + si_doc.save(ignore_permissions=True) + make_scheduler_log(e, si_doc.name) + +def make_scheduler_log(e, sales_invoice): + scheduler_log = frappe.new_doc('Scheduler Log') + scheduler_log.error = e + scheduler_log.sales_invoice = sales_invoice + scheduler_log.save(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index c3c91a0764..45c918ca8c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -17,17 +17,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.frm.set_df_property("debit_to", "print_hide", 0); } - // toggle to pos view if is_pos is 1 in user_defaults - if ((is_null(this.frm.doc.is_pos) && cint(frappe.defaults.get_user_default("is_pos"))===1) || this.frm.doc.is_pos) { - if(this.frm.doc.__islocal && !this.frm.doc.amended_from && !this.frm.doc.customer) { - this.frm.set_value("is_pos", 1); - this.is_pos(function() { - if (cint(frappe.defaults.get_user_defaults("fs_pos_view"))===1) - erpnext.pos.toggle(me.frm, true); - }); - } - } - erpnext.queries.setup_queries(this.frm, "Warehouse", function() { return erpnext.queries.warehouse(me.frm.doc); }); @@ -149,39 +138,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.get_terms(); }, - is_pos: function(doc, dt, dn, callback_fn) { - cur_frm.cscript.hide_fields(this.frm.doc); - if(cur_frm.doc.__missing_values_set) return; - if(cint(this.frm.doc.is_pos)) { - if(!this.frm.doc.company) { - this.frm.set_value("is_pos", 0); - msgprint(__("Please specify Company to proceed")); - } else { - var me = this; - return this.frm.call({ - doc: me.frm.doc, - method: "set_missing_values", - callback: function(r) { - if(!r.exc) { - if(r.message && r.message.print_format) { - cur_frm.pos_print_format = r.message.print_format; - } - cur_frm.doc.__missing_values_set = true; - me.frm.script_manager.trigger("update_stock"); - frappe.model.set_default_values(me.frm.doc); - me.set_dynamic_labels(); - me.calculate_taxes_and_totals(); - if(callback_fn) callback_fn(); - frappe.after_ajax(function() { - cur_frm.doc.__missing_values_set = false; - }) - } - } - }); - } - } - }, - customer: function() { var me = this; if(this.frm.updating_party_details) return; @@ -245,11 +201,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.write_off_outstanding_amount_automatically(); }, - paid_amount: function() { - this.set_in_company_currency(this.frm.doc, ["paid_amount"]); - this.write_off_outstanding_amount_automatically(); - }, - items_add: function(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 6678583932..9da4aaef0d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -3,6 +3,7 @@ "allow_import": 1, "allow_rename": 0, "autoname": "naming_series:", + "beta": 0, "creation": "2013-05-24 19:29:05", "custom": 0, "default_print_format": "Standard", @@ -194,6 +195,31 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "offline_pos_name", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Offline POS Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -653,6 +679,32 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "territory", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Territory", + "length": 0, + "no_copy": 0, + "options": "Territory", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1941,7 +1993,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 1, - "collapsible_depends_on": "paid_amount", + "collapsible_depends_on": "", "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", @@ -1971,7 +2023,7 @@ "depends_on": "is_pos", "fieldname": "cash_bank_account", "fieldtype": "Link", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -1996,9 +2048,34 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "depends_on": "is_pos", - "fieldname": "column_break3", - "fieldtype": "Column Break", + "fieldname": "payments", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Payments", + "length": 0, + "no_copy": 0, + "options": "Payments", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "section_break_84", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -2007,6 +2084,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -2014,35 +2092,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "is_pos", - "fieldname": "paid_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Paid Amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "paid_amount", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, "unique": 0 }, { @@ -2071,6 +2120,110 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "base_change_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Base Change Amount (Company Currency)", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_86", + "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, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "depends_on": "is_pos", + "fieldname": "paid_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Paid Amount", + "length": 0, + "no_copy": 1, + "oldfieldname": "paid_amount", + "oldfieldtype": "Currency", + "options": "currency", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "change_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Change Amount", + "length": 0, + "no_copy": 1, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -3443,7 +3596,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-04-14 13:01:01.748816", + "modified": "2016-05-09 15:03:33.236351", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -3530,6 +3683,7 @@ "write": 0 } ], + "quick_entry": 1, "read_only": 0, "read_only_onload": 1, "search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3120e73176..952cce6a00 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -64,8 +64,8 @@ class SalesInvoice(SellingController): self.validate_fixed_asset() self.set_income_account_for_fixed_assets() - if cint(self.is_pos): - self.validate_pos() + # if cint(self.is_pos): + # self.validate_pos() if cint(self.update_stock): self.validate_dropship_item() @@ -83,6 +83,18 @@ class SalesInvoice(SellingController): self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") self.update_packing_list() + def before_save(self): + set_account_for_mode_of_payment(self) + + def update_change_amount(self): + self.base_paid_amount = 0.0 + if self.paid_amount: + self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, self.precision("base_paid_amount")) + self.change_amount = self.base_change_amount = 0.0 + if self.paid_amount > self.grand_total: + self.change_amount = flt(self.paid_amount - self.grand_total, self.precision("change_amount")) + self.base_change_amount = flt(self.change_amount * self.conversion_rate, self.precision("base_change_amount")) + def on_submit(self): if not self.recurring_id: frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, @@ -588,8 +600,7 @@ class SalesInvoice(SellingController): gl_entries += super(SalesInvoice, self).get_gl_entries() def make_pos_gl_entries(self, gl_entries): - if cint(self.is_pos) and self.cash_bank_account and self.paid_amount: - bank_account_currency = get_account_currency(self.cash_bank_account) + if cint(self.is_pos) and self.paid_amount: # POS, make payment entries gl_entries.append( self.get_gl_dict({ @@ -597,22 +608,43 @@ class SalesInvoice(SellingController): "party_type": "Customer", "party": self.customer, "against": self.cash_bank_account, - "credit": self.base_paid_amount, - "credit_in_account_currency": self.base_paid_amount \ - if self.party_account_currency==self.company_currency else self.paid_amount, + "credit": flt(self.base_paid_amount - self.base_change_amount), + "credit_in_account_currency": flt(self.base_paid_amount - self.base_change_amount) \ + if self.party_account_currency==self.company_currency else flt(self.paid_amount - self.change_amount), "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, }, self.party_account_currency) ) - gl_entries.append( - self.get_gl_dict({ - "account": self.cash_bank_account, - "against": self.customer, - "debit": self.base_paid_amount, - "debit_in_account_currency": self.base_paid_amount \ - if bank_account_currency==self.company_currency else self.paid_amount - }, bank_account_currency) - ) + + cash_account = '' + for payment_mode in self.payments: + if payment_mode.type == 'Cash': + cash_account = payment_mode.account + + if payment_mode.base_amount > 0: + payment_mode_account_currency = get_account_currency(payment_mode.account) + gl_entries.append( + self.get_gl_dict({ + "account": payment_mode.account, + "against": self.customer, + "debit": payment_mode.base_amount, + "debit_in_account_currency": payment_mode.base_amount \ + if payment_mode_account_currency==self.company_currency else payment_mode.amount + }, payment_mode_account_currency) + ) + + if self.change_amount: + cash_account = cash_account or self.payments[0].account + cash_account_currency = get_account_currency(cash_account) + gl_entries.append( + self.get_gl_dict({ + "account": cash_account, + "against": self.customer, + "credit": self.base_change_amount, + "credit_in_account_currency": self.base_change_amount \ + if payment_mode_account_currency==self.company_currency else self.change_amount + }, payment_mode_account_currency) + ) def make_write_off_gl_entry(self, gl_entries): # write off entries, applicable if only pos @@ -680,7 +712,7 @@ def get_bank_cash_account(mode_of_payment, company): account = frappe.db.get_value("Mode of Payment Account", {"parent": mode_of_payment, "company": company}, "default_account") if not account: - frappe.msgprint(_("Please set default Cash or Bank account in Mode of Payment {0}").format(mode_of_payment)) + frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}").format(mode_of_payment)) return { "account": account } @@ -737,4 +769,9 @@ def make_delivery_note(source_name, target_doc=None): @frappe.whitelist() def make_sales_return(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc - return make_return_doc("Sales Invoice", source_name, target_doc) \ No newline at end of file + return make_return_doc("Sales Invoice", source_name, target_doc) + +def set_account_for_mode_of_payment(self): + for data in self.payments: + if not data.account: + data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account") \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index bac24d8027..7208c2af74 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -441,17 +441,45 @@ class TestSalesInvoice(unittest.TestCase): self.make_pos_profile() self._insert_purchase_receipt() - pos = copy.deepcopy(test_records[1]) pos["is_pos"] = 1 pos["update_stock"] = 1 - pos["cash_bank_account"] = "_Test Bank - _TC" - pos["paid_amount"] = 600.0 + pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300}, + {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300}] si = frappe.copy_doc(pos) si.insert() si.submit() + + self.assertEquals(si.paid_amount, 600.0) + self.pos_gl_entry(si, pos, 300) + + def test_make_pos_invoice(self): + from erpnext.accounts.doctype.sales_invoice.pos import make_invoice + + set_perpetual_inventory() + + self.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': 330}] + + invoice_data = [{'09052016142': pos}] + si = make_invoice(invoice_data) + self.assertEquals(si[0], '09052016142') + + 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.assertEquals(si.grand_total, 630.0) + + self.pos_gl_entry(si, pos, 330) + + def pos_gl_entry(self, si, pos, cash_amount): # check stock ledger entries sle = frappe.db.sql("""select * from `tabStock Ledger Entry` where voucher_type = 'Sales Invoice' and voucher_no = %s""", @@ -467,7 +495,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue(gl_entries) stock_in_hand = frappe.db.get_value("Account", {"warehouse": "_Test Warehouse - _TC"}) - + expected_gl_entries = sorted([ [si.debit_to, 630.0, 0.0], [pos["items"][0]["income_account"], 0.0, 500.0], @@ -475,8 +503,9 @@ class TestSalesInvoice(unittest.TestCase): [pos["taxes"][1]["account_head"], 0.0, 50.0], [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, 600.0], - ["_Test Bank - _TC", 600.0, 0.0] + [si.debit_to, 0.0, si.paid_amount], + ["_Test Bank - _TC", 300.0, 0.0], + ["Cash - _TC", cash_amount, 0.0] ]) for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)): diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index df7757b842..04dd5804d2 100644 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -1,46 +1,843 @@ +frappe.provide("erpnext.pos"); +{% include "erpnext/public/js/controllers/taxes_and_totals.js" %} + frappe.pages['pos'].on_page_load = function(wrapper) { var page = frappe.ui.make_app_page({ parent: wrapper, - title: __('Start Point-of-Sale (POS)'), + title: 'Point of Sale', single_column: true }); - - page.main.html(frappe.render_template("pos_page", {})); - - var pos_type = frappe.ui.form.make_control({ - parent: page.main.find(".select-type"), - df: { - fieldtype: "Select", - options: [ - {label: __("Billing (Sales Invoice)"), value:"Sales Invoice"}, - {value:"Sales Order"}, - {value:"Delivery Note"}, - {value:"Quotation"}, - {value:"Purchase Order"}, - {value:"Purchase Receipt"}, - {value:"Purchase Invoice"}, - ], - fieldname: "pos_type" - }, - only_input: true - }); - - pos_type.refresh(); - - pos_type.set_input("Sales Invoice"); - - page.main.find(".btn-primary").on("click", function() { - erpnext.open_as_pos = true; - new_doc(pos_type.get_value()); - }); - - $.ajax({ - url: "/api/resource/POS Profile", - success: function(data) { - if(!data.data.length) { - page.main.find(".pos-setting-message").removeClass('hide'); - } - } - }) - + + wrapper = $(wrapper).find('.page-content') + new erpnext.pos.PointOfSale(page, wrapper) } + +erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ + init: function(page, wrapper){ + this.page = page; + this.wrapper = wrapper; + this.set_indicator(); + this.onload(); + this.make_menu_list(); + this.set_interval_for_si_sync(); + this.si_docs = this.get_doc_from_localstorage(); + }, + + check_internet_connection: function(){ + var me = this; + //Check Internet connection after every 30 seconds + setInterval(function(){ + me.set_indicator(); + }, 30000) + }, + + set_indicator: function(){ + var me = this; + // navigator.onLine + this.page.set_indicator("Offline", "grey") + frappe.call({ + method:"frappe.handler.ping", + callback: function(r){ + if(r.message){ + me.connection_status = true; + me.page.set_indicator("Online", "green") + } + } + }) + }, + + onload: function(){ + var me = this; + + this.get_data_from_server(function(){ + me.create_new(); + }); + + this.check_internet_connection(); + }, + + make_menu_list: function(){ + var me = this; + + this.page.add_menu_item(__("Unsync Records"), function(){ + me.show_unsync_invoice_list() + }); + + this.page.add_menu_item(__("Sync Master Data"), function(){ + me.get_data_from_server() + }); + + this.page.add_menu_item(__("New Sales Invoice"), function() { + me.create_new() + }) + }, + + show_unsync_invoice_list: function(){ + var me = this; + this.si_docs = this.get_doc_from_localstorage(); + + this.list_dialog = new frappe.ui.Dialog({ + title: 'Invoice List' + }); + + this.list_dialog.show(); + this.list_body = this.list_dialog.body; + $(this.list_body).append('
\ +
Customer
\ +
Grand Total
\ +
Status
\ +
') + + $.each(this.si_docs, function(index, data){ + for(key in data) { + $(frappe.render_template("pos_invoice_list", { + name: key, + customer: data[key].customer, + grand_total: data[key].grand_total, + status: (data[key].docstatus == 1) ? 'Submitted' : 'Draft' + })).appendTo($(me.list_body)); + } + }) + + $(this.list_body).find('.list-row').click(function() { + me.name = $(this).attr('invoice-name') + doc_data = me.get_invoice_doc(me.si_docs) + if(doc_data){ + me.frm.doc = doc_data[0][me.name]; + me.refresh(); + me.disable_input_field(); + me.list_dialog.hide(); + } + }) + }, + + get_invoice_doc: function(si_docs){ + var me = this; + this.si_docs = this.get_doc_from_localstorage(); + + return $.grep(this.si_docs, function(data){ + for(key in data){ + return key == me.name + } + }) + }, + + get_data_from_server: function(callback){ + var me = this; + frappe.call({ + method: "erpnext.accounts.doctype.sales_invoice.pos.get_pos_data", + freeze: true, + 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; + localStorage.setItem('doc', JSON.stringify(r.message.doc)); + if(callback){ + callback(); + } + } + }) + }, + + create_new: function(){ + this.frm = {} + var me = this; + this.name = ''; + this.frm.doc = JSON.parse(localStorage.getItem('doc')) + this.load_data(); + this.setup(); + }, + + load_data: function(){ + this.items = window.items; + this.customers = window.customers; + this.pricing_rules = window.pricing_rules; + + $.each(window.meta, function(i, data){ + frappe.meta.sync(data) + }) + + this.print_template = frappe.render_template("print_template", + {content: window.print_template, title:"POS"}) + }, + + setup: function(){ + this.wrapper.html(frappe.render_template("pos", this.frm.doc)); + this.set_transaction_defaults("Customer"); + this.make(); + this.set_primary_action(); + }, + + set_transaction_defaults: function(party) { + var me = this; + this.party = party; + this.price_list = (party == "Customer" ? + this.frm.doc.selling_price_list : this.frm.doc.buying_price_list); + this.price_list_field = (party == "Customer" ? "selling_price_list" : "buying_price_list"); + this.sales_or_purchase = (party == "Customer" ? "Sales" : "Purchase"); + }, + + make: function() { + this.make_search(); + this.make_customer(); + this.make_item_list(); + this.make_discount_field() + }, + + make_search: function() { + var me = this; + this.search = frappe.ui.form.make_control({ + df: { + "fieldtype": "Data", + "label": "Item", + "fieldname": "pos_item", + "placeholder": "Search Item" + }, + parent: this.wrapper.find(".search-area"), + only_input: true, + }); + + this.search.make_input(); + this.search.$input.on("keyup", function() { + setTimeout(function() { + me.items = me.get_items(); + me.make_item_list(); + }, 1000); + }); + + this.party_field = frappe.ui.form.make_control({ + df: { + "fieldtype": "Data", + "options": this.party, + "label": this.party, + "fieldname": this.party.toLowerCase(), + "placeholder": this.party + }, + parent: this.wrapper.find(".party-area"), + only_input: true, + }); + + this.party_field.make_input(); + }, + + make_customer: function() { + var me = this; + + if(this.customers.length == 1){ + this.party_field.$input.val(this.customers[0].name); + this.frm.doc.customer = this.customers[0].name; + } + + this.party_field.$input.autocomplete({ + source: function (request, response) { + me.customer_data = me.get_customers(request.term) + response($.map(me.customer_data, function(data){ + return {label: data.name, value: data.name, + customer_group: data.customer_group, territory: data.territory} + })) + }, + change: function(event, ui){ + if(ui.item){ + me.frm.doc.customer = ui.item.label; + me.frm.doc.customer_name = ui.item.customer_name; + me.frm.doc.customer_group = ui.item.customer_group; + me.frm.doc.territory = ui.item.territory; + }else{ + me.frm.doc.customer = me.party_field.$input.val(); + } + me.refresh(); + } + }) + }, + + get_customers: function(key){ + var me = this; + key = key.toLowerCase() + return $.grep(this.customers, function(data) { + if(data.name.toLowerCase().match(key) || data.customer_name.toLowerCase().match(key) + || data.customer_group.toLowerCase().match(key)){ + return data + } + }) + }, + + make_item_list: function() { + var me = this; + if(!this.price_list) { + msgprint(__("Price List not found or disabled")); + return; + } + + me.item_timeout = null; + + var $wrap = me.wrapper.find(".item-list"); + me.wrapper.find(".item-list").empty(); + + if (this.items) { + $.each(this.items, function(index, obj) { + if(index < 16){ + $(frappe.render_template("pos_item", { + item_code: obj.name, + item_price: format_currency(obj.price_list_rate, obj.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), + abbr: frappe.get_abbr(obj.item_name) + })).tooltip().appendTo($wrap); + } + }); + } + + if(this.items.length == 1){ + this.search.$input.val(""); + this.add_to_cart(); + } + + // if form is local then allow this function + $(me.wrapper).find("div.pos-item").on("click", function() { + me.customer_validate(); + if(me.frm.doc.docstatus==0) { + me.items = me.get_items($(this).attr("data-item-code")) + me.add_to_cart(); + } + }); + }, + + get_items: function(item_code){ + // To search item as per the key enter + + var me = this; + this.item_serial_no = {} + + if(item_code){ + return $.grep(window.items, function(item){ + if(item.item_code == item_code ){ + return true + } + }) + } + + key = this.search.$input.val().toLowerCase(); + + if(key){ + return $.grep(window.items, function(item){ + if( (item.item_code.toLowerCase().match(key)) || + (item.item_name.toLowerCase().match(key)) || (item.item_group.toLowerCase().match(key)) ){ + return true + }else if(item.barcode){ + return item.barcode == me.search.$input.val() + } else if (in_list(item.serial_nos, me.search.$input.val())){ + me.item_serial_no[item.item_code] = me.search.$input.val() + return true + } + }) + }else{ + return window.items; + } + }, + + update_qty: function() { + var me = this; + + $(this.wrapper).find(".pos-item-qty").on("change", function(){ + var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); + me.update_qty_against_item_code(item_code, $(this).val()); + }) + + $(this.wrapper).find("[data-action='increase-qty']").on("click", function(){ + var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); + var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) + 1; + me.update_qty_against_item_code(item_code, qty); + }) + + $(this.wrapper).find("[data-action='decrease-qty']").on("click", function(){ + var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); + var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) - 1; + me.update_qty_against_item_code(item_code, qty); + }) + }, + + update_qty_against_item_code: function(item_code, qty){ + var me = this; + if(qty < 0){ + frappe.throw(__("Quantity must be positive")); + } + + this.remove_item = [] + $.each(this.frm.doc["items"] || [], function(i, d) { + if (d.item_code == item_code) { + d.qty = flt(qty); + d.amount = flt(d.rate) * flt(d.qty); + if(d.qty==0){ + me.remove_item.push(d.idx) + } + } + }); + this.remove_zero_qty_item(); + this.refresh(); + }, + + remove_zero_qty_item: function(){ + var me = this; + idx = 0 + this.items = [] + idx = 0 + $.each(this.frm.doc["items"] || [], function(i, d) { + if(!in_list(me.remove_item, d.idx)){ + d.idx = idx; + me.items.push(d); + idx++; + } + }); + + this.frm.doc["items"] = this.items; + }, + + make_discount_field: function(){ + var me = this; + + this.wrapper.find('input.discount-percentage').on("change", function() { + me.frm.doc.additional_discount_percentage = flt($(this).val(), precision("additional_discount_percentage")); + total = me.frm.doc.grand_total + + if(me.frm.doc.apply_discount_on == 'Net Total'){ + total = me.frm.doc.net_total + } + + me.frm.doc.discount_amount = flt(total*flt(me.frm.doc.additional_discount_percentage) / 100, precision("discount_amount")); + me.wrapper.find('input.discount-amount').val(me.frm.doc.discount_amount) + me.refresh(); + }); + + this.wrapper.find('input.discount-amount').on("change", function() { + me.frm.doc.discount_amount = flt($(this).val(), precision("discount_amount")); + me.frm.doc.additional_discount_percentage = 0.0; + me.wrapper.find('input.discount-percentage').val(0); + me.refresh(); + }); + }, + + customer_validate: function(){ + var me = this; + + if(!this.frm.doc.customer){ + frappe.throw(__("Please select customer")) + } + }, + + add_to_cart: function() { + var me = this; + var caught = false; + var no_of_items = me.wrapper.find(".pos-bill-item").length; + + this.validate_serial_no() + this.validate_warehouse(); + + if (no_of_items != 0) { + $.each(this.frm.doc["items"] || [], function(i, d) { + if (d.item_code == me.items[0].item_code) { + caught = true; + d.qty += 1; + d.amount = flt(d.rate) * flt(d.qty) + if(me.item_serial_no.length){ + d.serial_no += '\n' + me.item_serial_no[d.item_code] + } + } + }); + } + + // if item not found then add new item + if (!caught) + this.add_new_item_to_grid(); + + this.refresh(); + }, + + add_new_item_to_grid: function() { + var me = this; + this.child = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + " Item", "items"); + this.child.item_code = this.items[0].item_code; + this.child.item_name = this.items[0].item_name; + this.child.stock_uom = this.items[0].stock_uom; + 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.warehouse = 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.child.amount = flt(this.child.qty) * flt(this.child.rate); + this.child.serial_no = this.item_serial_no[this.child.item_code]; + }, + + refresh: function() { + var me = this; + this.refresh_fields(); + this.update_qty(); + this.set_primary_action(); + }, + refresh_fields: function() { + this.apply_pricing_rule(); + this.discount_amount_applied = false; + this._calculate_taxes_and_totals(); + this.calculate_discount_amount(); + this.show_items_in_item_cart(); + this.set_taxes(); + this.calculate_outstanding_amount(); + this.set_totals(); + }, + + get_company_currency: function() { + return erpnext.get_currency(this.frm.doc.company); + }, + + show_item_wise_taxes: function(){ + return null; + }, + + show_items_in_item_cart: function() { + var me = this; + var $items = this.wrapper.find(".items").empty(); + me.frm.doc.net_total = 0.0 + $.each(this.frm.doc.items|| [], function(i, d) { + $(frappe.render_template("pos_bill_item", { + 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, + projected_qty: d.projected_qty, + rate: format_currency(d.rate, me.frm.doc.currency), + amount: format_currency(d.amount, me.frm.doc.currency) + })).appendTo($items); + }); + + this.wrapper.find("input.pos-item-qty").on("focus", function() { + $(this).select(); + }); + }, + + set_taxes: function(){ + var me = this; + me.frm.doc.total_taxes_and_charges = 0.0 + + var taxes = this.frm.doc.taxes || []; + $(this.wrapper) + .find(".tax-area").toggleClass("hide", (taxes && taxes.length) ? false : true) + .find(".tax-table").empty(); + + $.each(taxes, function(i, d) { + if (d.tax_amount && cint(d.included_in_print_rate) == 0) { + $(frappe.render_template("pos_tax_row", { + description: d.description, + tax_amount: format_currency(flt(d.tax_amount_after_discount_amount), + me.frm.doc.currency) + })).appendTo(me.wrapper.find(".tax-table")); + } + }); + }, + + set_totals: function() { + var me = this; + this.wrapper.find(".net-total").text(format_currency(me.frm.doc.total, me.frm.doc.currency)); + this.wrapper.find(".grand-total").text(format_currency(me.frm.doc.grand_total, me.frm.doc.currency)); + }, + + set_primary_action: function() { + var me = this; + + if (this.frm.doc.docstatus==0 && this.frm.doc.outstanding_amount > 0) { + this.page.set_primary_action(__("Pay"), function() { + me.validate() + me.create_invoice(); + me.make_payment(); + }); + }else if(this.frm.doc.docstatus == 0){ + this.page.set_primary_action(__("Submit"), function() { + frappe.confirm(__("Do you really want to submit the invoice?"), function () { + me.write_off_amount() + }) + }) + }else if(this.frm.doc.docstatus == 1){ + this.page.set_primary_action(__("Print"), function() { + html = frappe.render(me.print_template, me.frm.doc) + frappe.require("/assets/js/print_format_v3.min.js", function() { + w = _p.preview(html); + setTimeout(function(){ + w.print(); + }, 1000) + }); + }) + } + }, + + write_off_amount: function(){ + var me = this; + var value = 0.0; + + if(this.frm.doc.outstanding_amount > 0){ + dialog = new frappe.ui.Dialog({ + title: 'Write Off Amount', + fields: [ + {fieldtype: "Currency", fieldname: "write_off_amount", label: __("Amount"), reqd: 1}, + ] + }); + + dialog.show(); + + dialog.fields_dict.write_off_amount.$input.change(function(){ + value = dialog.get_values() + }) + + dialog.set_primary_action(__("Submit"), function(){ + me.frm.doc.write_off_amount = value.write_off_amount; + me.calculate_outstanding_amount(); + dialog.hide(); + me.change_status(); + }) + }else{ + me.change_status(); + } + }, + + change_status: function(){ + if(this.frm.doc.docstatus == 0){ + this.frm.doc.docstatus = 1; + this.update_invoice(); + this.disable_input_field(); + } + }, + + disable_input_field: function(){ + var pointer_events = 'inherit' + $(this.wrapper).find('input').attr("disabled", false); + + if(this.frm.doc.docstatus == 1){ + pointer_events = 'none' + $(this.wrapper).find('input').attr("disabled", true); + } + + $(this.wrapper).find('.pos-bill-wrapper').css('pointer-events', pointer_events); + $(this.wrapper).find('.pos-items-section').css('pointer-events', pointer_events); + this.set_primary_action(); + }, + + create_invoice: function(){ + var me = this; + var invoice_data = {} + this.si_docs = this.get_doc_from_localstorage(); + if(this.name){ + this.update_invoice() + }else{ + this.name = $.now(); + invoice_data[this.name] = this.frm.doc + this.si_docs.push(invoice_data) + this.update_localstorage(); + this.set_primary_action(); + } + }, + + update_invoice: function(){ + var me = this; + this.si_docs = this.get_doc_from_localstorage(); + $.each(this.si_docs, function(index, data){ + for(key in data){ + if(key == me.name){ + me.si_docs[index][key] = me.frm.doc + me.update_localstorage(); + } + } + }) + }, + + update_localstorage: function(){ + try{ + localStorage.setItem('sales_invoice_doc', JSON.stringify(this.si_docs)); + }catch(e){ + frappe.throw(__("LocalStorage is full , did not save")) + } + }, + + get_doc_from_localstorage: function(){ + return JSON.parse(localStorage.getItem('sales_invoice_doc')) || []; + }, + + set_interval_for_si_sync: function(){ + var me = this; + setInterval(function(){ + me.sync_sales_invoice() + }, 6000) + }, + + sync_sales_invoice: function(){ + var me = this; + this.si_docs = this.get_submitted_invoice() + + if(this.connection_status && this.si_docs.length){ + frappe.call({ + method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice", + args: { + doc_list: me.si_docs + }, + callback: function(r){ + if(r.message){ + me.removed_items = r.message; + me.remove_doc_from_localstorage(); + } + } + }) + } + }, + + get_submitted_invoice: function(){ + invoices = [] + docs = this.get_doc_from_localstorage() + if(docs){ + invoices = $.map(docs, function(data){ + for(key in data){ + if(data[key].docstatus == 1){ + return data + } + } + }); + } + + return invoices + }, + + remove_doc_from_localstorage: function(){ + var me = this; + this.si_docs = this.get_doc_from_localstorage(); + if(this.removed_items){ + $.each(this.si_docs, function(index, data){ + for(key in data){ + if(in_list(me.removed_items, key)){ + me.si_docs.splice(index) + } + } + }) + this.update_localstorage(); + } + }, + + validate: function(){ + var me = this; + this.customer_validate(); + this.item_validate(); + }, + + item_validate: function(){ + if(this.frm.doc.items.length == 0){ + frappe.throw(__("Select items to save the invoice")) + } + }, + + validate_serial_no: function(){ + var me = this; + var item_code = serial_no = ''; + for (key in this.item_serial_no){ + item_code = key; + serial_no = me.item_serial_no[key] + } + + if(item_code && serial_no){ + $.each(this.frm.doc.items, function(index, data){ + if(data.item_code == item_code){ + if(in_list(data.serial_no.split('\n'), serial_no)){ + frappe.throw(__(repl("Serial no %(serial_no)s is already taken", { + 'serial_no': serial_no + }))) + } + } + }) + } + }, + + apply_pricing_rule: function(){ + var me = this; + $.each(this.frm.doc["items"], function(n, item) { + pricing_rule = me.get_pricing_rule(item) + me.validate_pricing_rule(pricing_rule) + if(pricing_rule.length){ + item.margin_type = pricing_rule[0].margin_type; + item.price_list_rate = pricing_rule[0].price || item.price_list_rate; + item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount; + item.discount_percentage = pricing_rule[0].discount_percentage || 0.0; + me.apply_pricing_rule_on_item(item) + } + }) + }, + + get_pricing_rule: function(item){ + var me = this; + return $.grep(this.pricing_rules, function(data){ + if(data.item_code == item.item_code || in_list(['All Item Groups', item.item_group], data.item_group)) { + if(in_list(['Customer', 'Customer Group', 'Territory'], data.applicable_for)){ + return me.validate_condition(data) + }else{ + return true + } + } + }) + }, + + validate_condition: function(data){ + //This method check condition based on applicable for + condition = this.get_mapper_for_pricing_rule(data)[data.applicable_for] + if(in_list(condition[1], condition[0])){ + return true + } + }, + + get_mapper_for_pricing_rule: function(data){ + return { + 'Customer': [data.customer, [this.doc.customer]], + 'Customer Group': [data.customer_group, [this.doc.customer_group, 'All Customer Groups']], + 'Territory': [data.territory, [this.doc.territory, 'All Territories']], + } + }, + + validate_pricing_rule: function(pricing_rule){ + //This method validate duplicate pricing rule + var pricing_rule_name = ''; + var priority = 0; + var pricing_rule_list = []; + var priority_list = [] + + if(pricing_rule.length > 1){ + + $.each(pricing_rule, function(index, data){ + pricing_rule_name += data.name + ',' + priority_list.push(data.priority) + if(priority <= data.priority){ + priority = data.priority + pricing_rule_list.push(data) + } + }) + + count = 0 + $.each(priority_list, function(index, value){ + if(value == priority){ + count++ + } + }) + + if(priority == 0 || count > 1){ + frappe.throw(__(repl("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: %(pricing_rule)s", { + 'pricing_rule': pricing_rule_name + }))) + } + + return pricing_rule_list + } + }, + + validate_warehouse: function(){ + if(!this.items[0].default_warehouse){ + frappe.throw(__("Deafault warehouse is required for selected item")) + } + } +}) \ No newline at end of file diff --git a/erpnext/accounts/party_status.py b/erpnext/accounts/party_status.py index ff095b1d65..5a638e571b 100644 --- a/erpnext/accounts/party_status.py +++ b/erpnext/accounts/party_status.py @@ -37,6 +37,7 @@ def notify_status(doc, method): party = frappe.get_doc(party_type, name) filters = get_filters_for(doc.doctype) + party.flags.ignore_mandatory = True status = None if filters: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 204c236ac6..2ede901ef3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -33,6 +33,7 @@ class AccountsController(TransactionBase): if self.meta.get_field("currency"): self.calculate_taxes_and_totals() + if not self.meta.get_field("is_return") or not self.is_return: self.validate_value("base_grand_total", ">=", 0) @@ -54,17 +55,19 @@ class AccountsController(TransactionBase): if not self.get("__islocal"): validate_recurring_document(self) convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) - - self.validate_paid_amount() - + + if self.doctype == 'Purchase Invoice': + self.validate_paid_amount() + def validate_paid_amount(self): if hasattr(self, "is_pos") or hasattr(self, "is_paid"): is_paid = self.get("is_pos") or self.get("is_paid") if cint(is_paid) == 1: - if flt(self.paid_amount) == 0: + if flt(self.paid_amount) == 0 and flt(self.outstanding_amount) > 0: if self.cash_bank_account: self.paid_amount = flt(flt(self.grand_total) - flt(self.write_off_amount), self.precision("paid_amount")) + self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, self.precision("base_paid_amount")) else: # show message that the amount is not paid self.paid_amount = 0 @@ -72,9 +75,6 @@ class AccountsController(TransactionBase): else: frappe.db.set(self,'paid_amount',0) - frappe.db.set(self, 'base_paid_amount', - flt(self.paid_amount*self.conversion_rate, self.precision("base_paid_amount"))) - def on_update_after_submit(self): if self.meta.get_field("is_recurring"): validate_recurring_document(self) @@ -510,7 +510,7 @@ class AccountsController(TransactionBase): 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)) - + @frappe.whitelist() def get_tax_rate(account_head): return frappe.db.get_value("Account", account_head, "tax_rate") diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 7210d76c03..eb75dee42a 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -435,13 +435,39 @@ class calculate_taxes_and_totals(object): - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total")) if self.doc.doctype == "Sales Invoice": + self.calculate_paid_amount() self.doc.round_floats_in(self.doc, ["paid_amount"]) paid_amount = self.doc.paid_amount \ if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount - self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount), - self.doc.precision("outstanding_amount")) + + self.doc.outstanding_amount = 0 + if total_amount_to_pay > paid_amount: + self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount), + self.doc.precision("outstanding_amount")) + self.change_amount() + elif self.doc.doctype == "Purchase Invoice": self.doc.outstanding_amount = flt(total_amount_to_pay, self.doc.precision("outstanding_amount")) + + def calculate_paid_amount(self): + paid_amount = base_paid_amount = 0.0 + for payment in self.doc.get('payments'): + payment.base_amount = flt(payment.amount * self.doc.conversion_rate) + paid_amount += payment.amount + base_paid_amount += payment.base_amount + + self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount")) + self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount")) + + def change_amount(self): + change_amount = 0.0 + if self.doc.paid_amount > self.doc.grand_total: + change_amount = flt(self.doc.paid_amount - self.doc.grand_total, + self.doc.precision("change_amount")) + + self.doc.change_amount = change_amount; + self.doc.base_change_amount = flt(change_amount * self.doc.conversion_rate, + self.doc.precision("base_change_amount")) def calculate_margin(self, item): total_margin = 0.0 diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 1831907f5e..9a949f2c3c 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -16,13 +16,18 @@ "public/js/templates/address_list.html", "public/js/templates/contact_list.html", "public/js/controllers/stock_controller.js", + "public/js/controllers/payments.js", "public/js/controllers/taxes_and_totals.js", "public/js/controllers/transaction.js", "public/js/pos/pos.html", "public/js/pos/pos_bill_item.html", "public/js/pos/pos_item.html", "public/js/pos/pos_tax_row.html", + "public/js/pos/pos_print.html", "public/js/pos/pos.js", + "public/js/pos/pos_invoice_list.html", + "public/js/payment/pos_payment.html", + "public/js/payment/payment_details.html", "public/js/templates/item_selector.html", "public/js/utils/item_selector.js" ], diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css index 6777e1e4d7..284260da7f 100644 --- a/erpnext/public/css/erpnext.css +++ b/erpnext/public/css/erpnext.css @@ -124,3 +124,49 @@ .dashboard-list-item:last-child { border-bottom: none; } + +.payment-toolbar { + margin-left: 35px; +} + +.payment-mode { + cursor: pointer; + font-family: sans-serif; + font-size: 15px; +} + +.pos-payment-row .col-xs-6 { + padding :10px; +} + +.pos-payment-row { + border-bottom:1px solid #d1d8dd; + margin: 2px 0px 5px 0px; +} + +.pos-payment-row:hover { + background-color: #FCFFDD; + cursor: pointer; +} + +.pos-keyboard-key, .delete-btn { + border: 1px solid #d1d8dd; + height:85px; + width:85px; + margin:10px 10px; + font-size:24px; + font-weight:200; +} + +.amount-label { + font-size: 16px; +} + +.selected-payment-mode { + background-color: #FCFFDD; + cursor: pointer; +} + +.pos-invoice-list { + padding: 15px 10px; +} diff --git a/erpnext/public/js/controllers/payments.js b/erpnext/public/js/controllers/payments.js new file mode 100644 index 0000000000..7ac2b7baea --- /dev/null +++ b/erpnext/public/js/controllers/payments.js @@ -0,0 +1,158 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +erpnext.payments = erpnext.stock.StockController.extend({ + make_payment: function() { + var me = this; + + this.dialog = new frappe.ui.Dialog({ + title: 'Payment' + }); + + this.dialog.show(); + this.$body = this.dialog.body; + this.dialog.$wrapper.find('.modal-dialog').css("width", "750px"); + this.set_payment_primary_action(); + this.make_keyboard(); + }, + + set_payment_primary_action: function(){ + var me = this; + + this.dialog.set_primary_action(__("Submit"), function() { + frappe.confirm(__("Do you really want to submit the invoice?"), function () { + me.write_off_amount(); + me.dialog.hide(); + }) + }) + }, + + make_keyboard: function(){ + var me = this; + $(this.$body).empty(); + $(this.$body).html(frappe.render_template('pos_payment', this.frm.doc)) + this.show_payment_details(); + this.bind_keyboard_event() + }, + + pay_amount: function(){ + var me = this; + this.make_multimode_payment(); + this.calculate_outstanding_amount() + this.show_payment_details(); + }, + + make_multimode_payment: function(){ + var me = this; + + if(this.frm.doc.change_amount > 0){ + me.payment_val = me.doc.outstanding_amount + } + + this.payments = frappe.model.add_child(this.frm.doc, 'Multi Mode Payment', "payments"); + this.payments.mode_of_payment = this.dialog.fields_dict.mode_of_payment.get_value(); + this.payments.amount = flt(this.payment_val); + }, + + show_payment_details: function(){ + var me = this; + multimode_payments = $(this.$body).find('.multimode-payments').empty(); + if(this.frm.doc.payments.length){ + $.each(this.frm.doc.payments, function(index, data){ + $(frappe.render_template('payment_details', { + mode_of_payment: data.mode_of_payment, + amount: data.amount, + idx: data.idx, + currency: me.frm.doc.currency, + type: data.type + })).appendTo(multimode_payments) + }) + }else{ + $("

No payment mode selected in pos profile

").appendTo(multimode_payments) + } + }, + + bind_keyboard_event: function(){ + var me = this; + this.payment_val = ''; + this.bind_payment_mode_keys_event(); + this.bind_keyboard_keys_event(); + }, + + bind_payment_mode_keys_event: function(){ + var me = this; + $(this.$body).find('.pos-payment-row').click(function(){ + me.idx = $(this).attr("idx"); + me.selected_mode = $(me.$body).find(repl("input[idx='%(idx)s']",{'idx': me.idx})); + me.highlight_selected_row() + me.payment_val = 0.0 + if(me.frm.doc.outstanding_amount > 0 && flt(me.selected_mode.val()) == 0.0){ + //When user first time click on row + me.payment_val = flt(me.frm.doc.outstanding_amount) + me.selected_mode.val(format_number(me.payment_val, 2)); + me.update_paid_amount() + me.bind_amount_change_event(); + }else if(flt(me.selected_mode.val()) > 0){ + //If user click on existing row which has value + me.payment_val = flt(me.selected_mode.val()); + } + me.selected_mode.select() + }) + }, + + highlight_selected_row: function(){ + var me = this; + selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']",{'idx': this.idx})); + $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode') + selected_row.addClass('selected-payment-mode') + $(this.$body).find('.amount').attr('disabled', true); + this.selected_mode.attr('disabled', false); + }, + + bind_keyboard_keys_event: function(){ + var me = this; + $(this.$body).find('.pos-keyboard-key').click(function(){ + me.payment_val += $(this).text(); + me.selected_mode.val(format_number(me.payment_val, 2)) + me.idx = me.selected_mode.attr("idx") + me.update_paid_amount() + }) + + $(this.$body).find('.delete-btn').click(function(){ + me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1); + me.selected_mode.val(format_number(me.payment_val, 2)); + me.idx = me.selected_mode.attr("idx") + me.update_paid_amount(); + }) + + }, + + bind_amount_change_event: function(){ + var me = this; + me.selected_mode.change(function(){ + me.payment_val = $(this).val() || 0.0; + me.selected_mode.val(format_number(me.payment_val, 2)) + me.idx = me.selected_mode.attr("idx") + me.update_paid_amount() + }) + }, + + update_paid_amount: function(){ + var me = this; + $.each(this.frm.doc.payments, function(index, data){ + if(cint(me.idx) == cint(data.idx)){ + data.amount = flt(me.selected_mode.val(), 2) + } + }) + this.calculate_outstanding_amount(); + this.show_amounts(); + }, + + show_amounts: function(){ + var me = this; + $(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency)); + $(this.$body).find('.change_amount').text(format_currency(this.frm.doc.change_amount, this.frm.doc.currency)) + $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, this.frm.doc.currency)) + this.update_invoice(); + } +}) \ No newline at end of file diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 1a234a3b38..f775f292f0 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -1,13 +1,30 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ +erpnext.taxes_and_totals = erpnext.payments.extend({ + apply_pricing_rule_on_item: function(item){ + if(!item.margin_type){ + item.margin_rate_or_amount = 0.0; + } + + if(item.margin_type == "Percentage"){ + item.total_margin = item.price_list_rate + item.price_list_rate * ( item.margin_rate_or_amount / 100); + }else{ + item.total_margin = item.price_list_rate + item.margin_rate_or_amount; + } + + item.rate = flt(item.total_margin , 2); + + if(item.discount_percentage){ + discount_value = flt(item.total_margin) * flt(item.discount_percentage) / 100; + item.rate = flt((item.total_margin) - (discount_value), precision('rate')); + } + }, + calculate_taxes_and_totals: function(update_paid_amount) { this.discount_amount_applied = false; this._calculate_taxes_and_totals(); - - if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) - this.apply_discount_amount(); + this.calculate_discount_amount(); // Advance calculation applicable to Sales /Purchase Invoice if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype) @@ -24,6 +41,11 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ this.frm.refresh_fields(); }, + calculate_discount_amount: function(){ + if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) + this.apply_discount_amount(); + }, + _calculate_taxes_and_totals: function() { this.validate_conversion_rate(); this.calculate_item_values(); @@ -98,7 +120,7 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ $.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0 }); - if (!this.discount_amount_applied) { + if (!this.discount_amount_applied && cur_frm) { cur_frm.cscript.validate_taxes_and_charges(tax.doctype, tax.name); me.validate_inclusive_tax(tax); } @@ -433,13 +455,14 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ apply_discount_amount: function() { var me = this; var distributed_amount = 0.0; + this.frm.doc.base_discount_amount = 0.0; if (this.frm.doc.discount_amount) { if(!this.frm.doc.apply_discount_on) frappe.throw(__("Please select Apply Discount On")); - - this.frm.set_value("base_discount_amount", - flt(this.frm.doc.discount_amount * this.frm.doc.conversion_rate, precision("base_discount_amount"))) + + this.frm.doc.base_discount_amount = flt(this.frm.doc.discount_amount * this.frm.doc.conversion_rate, + precision("base_discount_amount")); var total_for_discount_amount = this.get_total_for_discount_amount(); // calculate item amount after Discount Amount @@ -455,8 +478,6 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ this.discount_amount_applied = true; this._calculate_taxes_and_totals(); } - } else { - this.frm.set_value("base_discount_amount", 0); } }, @@ -524,18 +545,59 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ this.frm.doc.paid_amount = 0 } this.set_in_company_currency(this.frm.doc, ["paid_amount"]); - this.frm.refresh_field("paid_amount"); - this.frm.refresh_field("base_paid_amount"); + + if(this.frm.refresh_field){ + this.frm.refresh_field("paid_amount"); + this.frm.refresh_field("base_paid_amount"); + } + + if(this.frm.doc.doctype == "Sales Invoice"){ + this.calculate_paid_amount() + } + + var outstanding_amount = 0.0 var paid_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ? this.frm.doc.paid_amount : this.frm.doc.base_paid_amount; - var outstanding_amount = flt(total_amount_to_pay - flt(paid_amount), - precision("outstanding_amount")); + if (total_amount_to_pay > paid_amount){ + outstanding_amount = flt(total_amount_to_pay - flt(paid_amount), + precision("outstanding_amount")); + } } else if(this.frm.doc.doctype == "Purchase Invoice") { var outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount")); } - this.frm.set_value("outstanding_amount", outstanding_amount); - } + + this.frm.doc.outstanding_amount = outstanding_amount; + this.calculate_change_amount() + }, + + calculate_paid_amount: function(){ + var me = this; + var paid_amount = base_paid_amount = 0.0; + $.each(this.frm.doc['payments'] || [], function(index, data){ + if(data.amount > 0){ + data.base_amount = flt(data.amount * me.frm.doc.conversion_rate); + paid_amount += data.amount; + base_paid_amount += data.base_amount; + } + }) + + this.frm.doc.paid_amount = flt(paid_amount, precision("paid_amount")); + this.frm.doc.base_paid_amount = flt(base_paid_amount, precision("base_paid_amount")); + }, + + calculate_change_amount: function(){ + var change_amount = 0.0; + if(this.frm.doc.paid_amount > this.frm.doc.grand_total){ + change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total, + precision("change_amount")) + } + + this.frm.doc.change_amount = flt(change_amount, + precision("change_amount")) + this.frm.doc.base_change_amount = flt(change_amount * this.frm.doc.conversion_rate, + precision("base_change_amount")) + }, }) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3f6ea5ae9c..c6224668bc 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -165,7 +165,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ erpnext.hide_company(); this.show_item_wise_taxes(); this.set_dynamic_labels(); - erpnext.pos.make_pos_btn(this.frm); + // erpnext.pos.make_pos_btn(this.frm); this.setup_sms(); this.make_show_payments_btn(); }, @@ -550,7 +550,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ setup_field_label_map(["base_total", "base_net_total", "base_total_taxes_and_charges", "base_discount_amount", "base_grand_total", "base_rounded_total", "base_in_words", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", "total_amount_to_pay", - "base_paid_amount", "base_write_off_amount" + "base_paid_amount", "base_write_off_amount", "base_change_amount" ], company_currency); setup_field_label_map(["total", "net_total", "total_taxes_and_charges", "discount_amount", diff --git a/erpnext/public/js/payment/payment_details.html b/erpnext/public/js/payment/payment_details.html new file mode 100644 index 0000000000..b0f61d81f7 --- /dev/null +++ b/erpnext/public/js/payment/payment_details.html @@ -0,0 +1,4 @@ +
+
{{mode_of_payment}}
+
+
\ No newline at end of file diff --git a/erpnext/public/js/payment/pos_payment.html b/erpnext/public/js/payment/pos_payment.html new file mode 100644 index 0000000000..e93644dc28 --- /dev/null +++ b/erpnext/public/js/payment/pos_payment.html @@ -0,0 +1,40 @@ +
+
+
+
+

Total

{%= format_currency(grand_total, currency) %}

+
+
+

Paid

+
+
+
+
+
+
+
+
+
+

Outstanding

{%= format_currency(outstanding_amount, currency) %}

+
+
+

Change

{%= format_currency(change_amount, currency) %}

+
+
+
+
+ {% for(var i=0; i<3; i++) { %} +
+ {% for(var j=i*3; j<(i+1)*3; j++) { %} + + {% } %} +
+ {% } %} +
+ + + +
+
+
+
diff --git a/erpnext/public/js/pos/pos.html b/erpnext/public/js/pos/pos.html index 36ef7c958c..9d0ab60fb0 100644 --- a/erpnext/public/js/pos/pos.html +++ b/erpnext/public/js/pos/pos.html @@ -24,22 +24,24 @@
-
-
{%= __("Discount") %}
-
-
- % - + {% if (apply_discount_on) { %} +
+
{%= __("Discount") %}
+
+
+ % + +
-
-
-
- {%= get_currency_symbol(currency) %} - +
+
+ {%= get_currency_symbol(currency) %} + +
-
-
+
+ {% } %}
{%= __("Grand Total") %}
@@ -51,11 +53,12 @@
+
-
+
diff --git a/erpnext/public/js/pos/pos_bill_item.html b/erpnext/public/js/pos/pos_bill_item.html index c93c76caa6..fe521f6fb9 100644 --- a/erpnext/public/js/pos/pos_bill_item.html +++ b/erpnext/public/js/pos/pos_bill_item.html @@ -9,8 +9,7 @@
{% if(actual_qty != null) { %}
- {%= actual_qty || 0 %} - ({%= projected_qty || 0 %}) + {%= actual_qty || 0 %}
{% } %}
diff --git a/erpnext/public/js/pos/pos_invoice_list.html b/erpnext/public/js/pos/pos_invoice_list.html new file mode 100644 index 0000000000..463b3e7e5f --- /dev/null +++ b/erpnext/public/js/pos/pos_invoice_list.html @@ -0,0 +1,5 @@ +
+
{%= customer %}
+
{%= grand_total %}
+
{%= status %}
+
diff --git a/erpnext/public/js/pos/pos_print.html b/erpnext/public/js/pos/pos_print.html new file mode 100644 index 0000000000..d31a88568f --- /dev/null +++ b/erpnext/public/js/pos/pos_print.html @@ -0,0 +1,25 @@ + + +

+ {{ company }}
+

+

+ {{currency}} +

+{% for item in items %} +

{{item.item_code}}

+{% endfor %} \ No newline at end of file diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 5906a4fba4..e613daf84a 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -122,7 +122,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ // check if child doctype is Sales Order Item/Qutation Item and calculate the rate if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), cdt) - this.calculate_revised_margin_and_rate(item, doc,cdt, cdn); + this.apply_pricing_rule_on_item(item); else item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), precision("rate", item)); @@ -320,7 +320,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ margin_rate_or_amount: function(doc, cdt, cdn) { // calculated the revised total margin and rate on margin rate changes item = locals[cdt][cdn]; - this.calculate_revised_margin_and_rate(item) + this.apply_pricing_rule_on_item(item) this.calculate_taxes_and_totals(); cur_frm.refresh_fields(); }, @@ -328,26 +328,9 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ margin_type: function(doc, cdt, cdn){ // calculate the revised total margin and rate on margin type changes item = locals[cdt][cdn]; - this.calculate_revised_margin_and_rate(item, doc,cdt, cdn) + this.apply_pricing_rule_on_item(item, doc,cdt, cdn) this.calculate_taxes_and_totals(); cur_frm.refresh_fields(); - }, - - calculate_revised_margin_and_rate: function(item){ - if(in_list(["Percentage", "Amount"], item.margin_type)){ - if(item.margin_type == "Percentage") - item.total_margin = item.price_list_rate + item.price_list_rate * ( item.margin_rate_or_amount / 100); - else - item.total_margin = item.price_list_rate + item.margin_rate_or_amount; - item.rate = flt(item.total_margin * (1 - item.discount_percentage / 100.0), - precision("rate", item)); - } - else{ - item.rate_or_amount = 0.0; - item.total_margin = 0.0; - item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), - precision("rate", item)); - } } });