Merge branch 'develop' of https://github.com/frappe/erpnext into rebrand-ui
This commit is contained in:
		
						commit
						11111cd9a9
					
				| @ -148,10 +148,15 @@ | ||||
|    "link_to": "Trial Balance", | ||||
|    "type": "Report" | ||||
|   }, | ||||
|   { | ||||
|    "label": "Point of Sale", | ||||
|    "link_to": "point-of-sale", | ||||
|    "type": "Page" | ||||
|   }, | ||||
|   { | ||||
|    "label": "Dashboard", | ||||
|    "link_to": "Accounts", | ||||
|    "type": "Dashboard" | ||||
|   } | ||||
|  ] | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -26,22 +26,22 @@ def test_create_test_data(): | ||||
| 		"item_group": "_Test Item Group", | ||||
| 		"item_name": "_Test Tesla Car", | ||||
| 		"apply_warehouse_wise_reorder_level": 0, | ||||
| 		"warehouse":"_Test Warehouse - _TC", | ||||
| 		"warehouse":"Stores - TCP1", | ||||
| 		"gst_hsn_code": "999800", | ||||
| 		"valuation_rate": 5000, | ||||
| 		"standard_rate":5000, | ||||
| 		"item_defaults": [{ | ||||
| 		"company": "_Test Company", | ||||
| 		"default_warehouse": "_Test Warehouse - _TC", | ||||
| 		"company": "_Test Company with perpetual inventory", | ||||
| 		"default_warehouse": "Stores - TCP1", | ||||
| 		"default_price_list":"_Test Price List", | ||||
| 		"expense_account": "_Test Account Cost for Goods Sold - _TC", | ||||
| 		"buying_cost_center": "_Test Cost Center - _TC", | ||||
| 		"selling_cost_center": "_Test Cost Center - _TC", | ||||
| 		"income_account": "Sales - _TC" | ||||
| 		"expense_account": "Cost of Goods Sold - TCP1", | ||||
| 		"buying_cost_center": "Main - TCP1", | ||||
| 		"selling_cost_center": "Main - TCP1", | ||||
| 		"income_account": "Sales - TCP1" | ||||
| 		}], | ||||
| 		"show_in_website": 1, | ||||
| 		"route":"-test-tesla-car", | ||||
| 		"website_warehouse": "_Test Warehouse - _TC" | ||||
| 		"website_warehouse": "Stores - TCP1" | ||||
| 		}) | ||||
| 		item.insert() | ||||
| 	# create test item price | ||||
| @ -63,12 +63,12 @@ def test_create_test_data(): | ||||
| 		"items": [{ | ||||
| 			"item_code": "_Test Tesla Car" | ||||
| 		}], | ||||
| 		"warehouse":"_Test Warehouse - _TC", | ||||
| 		"warehouse":"Stores - TCP1", | ||||
| 		"coupon_code_based":1, | ||||
| 		"selling": 1, | ||||
| 		"rate_or_discount": "Discount Percentage", | ||||
| 		"discount_percentage": 30, | ||||
| 		"company": "_Test Company", | ||||
| 		"company": "_Test Company with perpetual inventory", | ||||
| 		"currency":"INR", | ||||
| 		"for_price_list":"_Test Price List" | ||||
| 		}) | ||||
| @ -112,7 +112,10 @@ class TestCouponCode(unittest.TestCase): | ||||
| 		self.assertEqual(coupon_code.get("used"),0) | ||||
| 
 | ||||
| 	def test_2_sales_order_with_coupon_code(self): | ||||
| 		so = make_sales_order(customer="_Test Customer",selling_price_list="_Test Price List",item_code="_Test Tesla Car", rate=5000,qty=1, do_not_submit=True) | ||||
| 		so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', | ||||
| 			customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1, | ||||
| 			do_not_submit=True) | ||||
| 
 | ||||
| 		so = frappe.get_doc('Sales Order', so.name) | ||||
| 		# check item price before coupon code is applied | ||||
| 		self.assertEqual(so.items[0].rate, 5000) | ||||
| @ -120,7 +123,7 @@ class TestCouponCode(unittest.TestCase): | ||||
| 		so.sales_partner='_Test Coupon Partner' | ||||
| 		so.save() | ||||
| 		# check item price after coupon code is applied | ||||
| 		self.assertEqual(so.items[0].rate, 3500)	 | ||||
| 		self.assertEqual(so.items[0].rate, 3500) | ||||
| 		so.submit() | ||||
| 
 | ||||
| 	def test_3_check_coupon_code_used_after_so(self): | ||||
|  | ||||
| @ -6,6 +6,7 @@ import unittest, frappe | ||||
| from frappe.utils import flt, nowdate | ||||
| from erpnext.accounts.doctype.account.test_account import get_inventory_account | ||||
| from erpnext.exceptions import InvalidAccountCurrency | ||||
| from erpnext.accounts.general_ledger import StockAccountInvalidTransaction | ||||
| 
 | ||||
| class TestJournalEntry(unittest.TestCase): | ||||
| 	def test_journal_entry_with_against_jv(self): | ||||
| @ -81,19 +82,46 @@ class TestJournalEntry(unittest.TestCase): | ||||
| 		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory | ||||
| 		set_perpetual_inventory() | ||||
| 
 | ||||
| 		jv = frappe.copy_doc(test_records[0]) | ||||
| 		jv = frappe.copy_doc({ | ||||
| 			"cheque_date": nowdate(), | ||||
| 			"cheque_no": "33", | ||||
| 			"company": "_Test Company with perpetual inventory", | ||||
| 			"doctype": "Journal Entry", | ||||
| 			"accounts": [ | ||||
| 			{ | ||||
| 				"account": "Debtors - TCP1", | ||||
| 				"party_type": "Customer", | ||||
| 				"party": "_Test Customer", | ||||
| 				"credit_in_account_currency": 400.0, | ||||
| 				"debit_in_account_currency": 0.0, | ||||
| 				"doctype": "Journal Entry Account", | ||||
| 				"parentfield": "accounts", | ||||
| 				"cost_center": "Main - TCP1" | ||||
| 			}, | ||||
| 			{ | ||||
| 				"account": "_Test Bank - TCP1", | ||||
| 				"credit_in_account_currency": 0.0, | ||||
| 				"debit_in_account_currency": 400.0, | ||||
| 				"doctype": "Journal Entry Account", | ||||
| 				"parentfield": "accounts", | ||||
| 				"cost_center": "Main - TCP1" | ||||
| 			} | ||||
| 			], | ||||
| 			"naming_series": "_T-Journal Entry-", | ||||
| 			"posting_date": nowdate(), | ||||
| 			"user_remark": "test", | ||||
| 			"voucher_type": "Bank Entry" | ||||
| 			}) | ||||
| 
 | ||||
| 		jv.get("accounts")[0].update({ | ||||
| 			"account": get_inventory_account('_Test Company'), | ||||
| 			"company": "_Test Company", | ||||
| 			"account": get_inventory_account('_Test Company with perpetual inventory'), | ||||
| 			"company": "_Test Company with perpetual inventory", | ||||
| 			"party_type": None, | ||||
| 			"party": None | ||||
| 		}) | ||||
| 
 | ||||
| 		jv.insert() | ||||
| 
 | ||||
| 		from erpnext.accounts.general_ledger import StockAccountInvalidTransaction | ||||
| 		self.assertRaises(StockAccountInvalidTransaction, jv.submit) | ||||
| 
 | ||||
| 		jv.cancel() | ||||
| 		set_perpetual_inventory(0) | ||||
| 
 | ||||
| 	def test_multi_currency(self): | ||||
|  | ||||
| @ -1,426 +1,123 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "autoname": "",  | ||||
|  "beta": 0,  | ||||
|  "creation": "2018-01-23 05:40:18.117583",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "creation": "2018-01-23 05:40:18.117583", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "loyalty_program", | ||||
|   "loyalty_program_tier", | ||||
|   "customer", | ||||
|   "invoice_type", | ||||
|   "invoice", | ||||
|   "redeem_against", | ||||
|   "loyalty_points", | ||||
|   "purchase_amount", | ||||
|   "expiry_date", | ||||
|   "posting_date", | ||||
|   "company" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "loyalty_program",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Loyalty Program",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Loyalty Program",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "loyalty_program", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Loyalty Program", | ||||
|    "options": "Loyalty Program" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "loyalty_program_tier",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Loyalty Program Tier",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "loyalty_program_tier", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Loyalty Program Tier" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "customer",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Customer",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Customer",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "customer", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Customer", | ||||
|    "options": "Customer" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "sales_invoice",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Sales Invoice",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Sales Invoice",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "redeem_against", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Redeem Against", | ||||
|    "options": "Loyalty Point Entry" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "redeem_against",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Redeem Against",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Loyalty Point Entry",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "loyalty_points", | ||||
|    "fieldtype": "Int", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Loyalty Points" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "loyalty_points",  | ||||
|    "fieldtype": "Int",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Loyalty Points",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "purchase_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Purchase Amount" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "purchase_amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Purchase Amount",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "expiry_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Expiry Date" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "expiry_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Expiry Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "posting_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Posting Date" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "posting_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Posting Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Company", | ||||
|    "options": "Company" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "company",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Company",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Company",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "fieldname": "invoice_type", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Invoice Type", | ||||
|    "options": "DocType" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "invoice", | ||||
|    "fieldtype": "Dynamic Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Invoice", | ||||
|    "options": "invoice_type" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 1,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2018-08-29 16:05:22.810347",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Loyalty Point Entry",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  ], | ||||
|  "in_create": 1, | ||||
|  "modified": "2020-01-30 17:27:55.964242", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Loyalty Point Entry", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 0,  | ||||
|    "delete": 0,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Auditor",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 0,  | ||||
|    "submit": 0,  | ||||
|    "write": 0 | ||||
|   },  | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Auditor" | ||||
|   }, | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 0,  | ||||
|    "delete": 0,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Accounts Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 0,  | ||||
|    "submit": 0,  | ||||
|    "write": 0 | ||||
|   },  | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts Manager" | ||||
|   }, | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 0,  | ||||
|    "delete": 0,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Accounts User",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 0,  | ||||
|    "submit": 0,  | ||||
|    "write": 0 | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User" | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "title_field": "customer",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "title_field": "customer", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -18,7 +18,7 @@ def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=No | ||||
| 		date = today() | ||||
| 
 | ||||
| 	return frappe.db.sql(''' | ||||
| 		select name, loyalty_points, expiry_date, loyalty_program_tier, sales_invoice | ||||
| 		select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice | ||||
| 		from `tabLoyalty Point Entry` | ||||
| 		where customer=%s and loyalty_program=%s | ||||
| 			and expiry_date>=%s and loyalty_points>0 and company=%s | ||||
|  | ||||
| @ -36,7 +36,8 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non | ||||
| 		return {"loyalty_points": 0, "total_spent": 0} | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0): | ||||
| def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, \ | ||||
| 		silent=False, include_expired_entry=False, current_transaction_amount=0): | ||||
| 	lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent) | ||||
| 	loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program) | ||||
| 	lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)) | ||||
| @ -59,10 +60,10 @@ def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None | ||||
| 	if not loyalty_program: | ||||
| 		loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program") | ||||
| 
 | ||||
| 		if not (loyalty_program or silent): | ||||
| 		if not loyalty_program and not silent: | ||||
| 			frappe.throw(_("Customer isn't enrolled in any Loyalty Program")) | ||||
| 		elif silent and not loyalty_program: | ||||
| 			return frappe._dict({"loyalty_program": None}) | ||||
| 			return frappe._dict({"loyalty_programs": None}) | ||||
| 
 | ||||
| 	if not company: | ||||
| 		company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name | ||||
|  | ||||
| @ -27,7 +27,7 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 		customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"}) | ||||
| 		earned_points = get_points_earned(si_original) | ||||
| 
 | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 
 | ||||
| 		self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program) | ||||
| 		self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier) | ||||
| @ -42,8 +42,8 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 
 | ||||
| 		earned_after_redemption = get_points_earned(si_redeem) | ||||
| 
 | ||||
| 		lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'redeem_against': lpe.name}) | ||||
| 		lpe_earn = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]}) | ||||
| 		lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name}) | ||||
| 		lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]}) | ||||
| 
 | ||||
| 		self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption) | ||||
| 		self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points)) | ||||
| @ -66,7 +66,7 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 
 | ||||
| 		earned_points = get_points_earned(si_original) | ||||
| 
 | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 
 | ||||
| 		self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program) | ||||
| 		self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier) | ||||
| @ -82,8 +82,8 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 		customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"}) | ||||
| 		earned_after_redemption = get_points_earned(si_redeem) | ||||
| 
 | ||||
| 		lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'redeem_against': lpe.name}) | ||||
| 		lpe_earn = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]}) | ||||
| 		lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name}) | ||||
| 		lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]}) | ||||
| 
 | ||||
| 		self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption) | ||||
| 		self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points)) | ||||
| @ -101,7 +101,7 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 		si.insert() | ||||
| 		si.submit() | ||||
| 
 | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si.name, 'customer': si.customer}) | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si.name, 'customer': si.customer}) | ||||
| 		self.assertEqual(True, not (lpe is None)) | ||||
| 
 | ||||
| 		# cancelling sales invoice | ||||
| @ -118,7 +118,7 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 		si_original.submit() | ||||
| 
 | ||||
| 		earned_points = get_points_earned(si_original) | ||||
| 		lpe_original = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 		lpe_original = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 		self.assertEqual(lpe_original.loyalty_points, earned_points) | ||||
| 
 | ||||
| 		# create sales invoice return | ||||
| @ -130,10 +130,10 @@ class TestLoyaltyProgram(unittest.TestCase): | ||||
| 		si_return.submit() | ||||
| 
 | ||||
| 		# fetch original invoice again as its status would have been updated | ||||
| 		si_original = frappe.get_doc('Sales Invoice', lpe_original.sales_invoice) | ||||
| 		si_original = frappe.get_doc('Sales Invoice', lpe_original.invoice) | ||||
| 
 | ||||
| 		earned_points = get_points_earned(si_original) | ||||
| 		lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 		lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer}) | ||||
| 		self.assertEqual(lpe_after_return.loyalty_points, earned_points) | ||||
| 		self.assertEqual(True, (lpe_original.loyalty_points > lpe_after_return.loyalty_points)) | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,7 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 		}); | ||||
| 		frm.set_query("party_type", function() { | ||||
| 			return{ | ||||
| 				"filters": { | ||||
| 				filters: { | ||||
| 					"name": ["in", Object.keys(frappe.boot.party_account_types)], | ||||
| 				} | ||||
| 			} | ||||
| @ -33,7 +33,7 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 		frm.set_query("party_bank_account", function() { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					"is_company_account":0, | ||||
| 					is_company_account: 0, | ||||
| 					party_type: frm.doc.party_type, | ||||
| 					party: frm.doc.party | ||||
| 				} | ||||
| @ -42,7 +42,7 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 		frm.set_query("bank_account", function() { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					"is_company_account":1 | ||||
| 					is_company_account: 1 | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| @ -342,7 +342,7 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 							() => { | ||||
| 								frm.set_party_account_based_on_party = false; | ||||
| 								if (r.message.bank_account) { | ||||
| 									frm.set_value("party_bank_account", r.message.bank_account); | ||||
| 									frm.set_value("bank_account", r.message.bank_account); | ||||
| 								} | ||||
| 							} | ||||
| 						]); | ||||
|  | ||||
| @ -911,7 +911,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre | ||||
| 	elif reference_doctype != "Journal Entry": | ||||
| 		if party_account_currency == company_currency: | ||||
| 			if ref_doc.doctype == "Expense Claim": | ||||
| 				total_amount = ref_doc.total_sanctioned_amount | ||||
| 				total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges) | ||||
| 			elif ref_doc.doctype == "Employee Advance": | ||||
| 				total_amount = ref_doc.advance_amount | ||||
| 			else: | ||||
| @ -929,8 +929,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre | ||||
| 			outstanding_amount = ref_doc.get("outstanding_amount") | ||||
| 			bill_no = ref_doc.get("bill_no") | ||||
| 		elif reference_doctype == "Expense Claim": | ||||
| 			outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \ | ||||
| 				- flt(ref_doc.get("total_amount+reimbursed")) - flt(ref_doc.get("total_advance_amount")) | ||||
| 			outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\ | ||||
| 				- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount")) | ||||
| 		elif reference_doctype == "Employee Advance": | ||||
| 			outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount) | ||||
| 		else: | ||||
|  | ||||
| @ -140,9 +140,6 @@ class PaymentRequest(Document): | ||||
| 		}) | ||||
| 
 | ||||
| 	def set_as_paid(self): | ||||
| 		if frappe.session.user == "Guest": | ||||
| 			frappe.set_user("Administrator") | ||||
| 
 | ||||
| 		payment_entry = self.create_payment_entry() | ||||
| 		self.make_invoice() | ||||
| 
 | ||||
| @ -254,7 +251,7 @@ class PaymentRequest(Document): | ||||
| 
 | ||||
| 		if status in ["Authorized", "Completed"]: | ||||
| 			redirect_to = None | ||||
| 			self.run_method("set_as_paid") | ||||
| 			self.set_as_paid() | ||||
| 
 | ||||
| 			# if shopping cart enabled and in session | ||||
| 			if (shopping_cart_settings.enabled and hasattr(frappe.local, "session") | ||||
|  | ||||
| @ -12,15 +12,15 @@ | ||||
| 								</thead> | ||||
| 								<tbody> | ||||
| 									<tr> | ||||
| 										<td class="text-left">{{ _('Grand Total') }}</td> | ||||
| 										<td class='text-right'>{{ data.grand_total or '' }} {{ currency.symbol }}</td> | ||||
| 										<td class="text-left font-bold">{{ _('Grand Total') }}</td> | ||||
| 										<td class='text-right'> {{ frappe.utils.fmt_money(data.grand_total or '', currency=currency) }}</td> | ||||
| 									</tr> | ||||
| 									<tr> | ||||
| 										<td class="text-left">{{ _('Net Total') }}</td> | ||||
| 										<td class='text-right'>{{ data.net_total or '' }} {{ currency.symbol }}</td> | ||||
| 										<td class="text-left font-bold">{{ _('Net Total') }}</td> | ||||
| 										<td class='text-right'> {{ frappe.utils.fmt_money(data.net_total or '', currency=currency) }}</td> | ||||
| 									</tr> | ||||
| 									<tr> | ||||
| 										<td class="text-left">{{ _('Total Quantity') }}</td> | ||||
| 										<td class="text-left font-bold">{{ _('Total Quantity') }}</td> | ||||
| 										<td class='text-right'>{{ data.total_quantity or '' }}</td> | ||||
| 									</tr> | ||||
| 
 | ||||
| @ -45,7 +45,7 @@ | ||||
| 								{% for d in data.payment_reconciliation %} | ||||
| 									<tr> | ||||
| 										<td class="text-left">{{ d.mode_of_payment }}</td> | ||||
| 										<td class='text-right'>{{ d.expected_amount }} {{ currency.symbol }}</td> | ||||
| 										<td class='text-right'> {{ frappe.utils.fmt_money(d.expected_amount - d.opening_amount, currency=currency) }}</td> | ||||
| 									</tr> | ||||
| 								{% endfor %} | ||||
| 							</tbody> | ||||
| @ -55,12 +55,14 @@ | ||||
| 				<!-- Section end --> | ||||
| 
 | ||||
| 				<!-- Taxes section --> | ||||
| 				{% if data.taxes %} | ||||
| 				<div> | ||||
| 						<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Taxes") }}</h6> | ||||
| 						<div class="tax-break-up" style="overflow-x: auto;"> | ||||
| 							<table class="table table-bordered table-hover"> | ||||
| 								<thead> | ||||
| 									<tr> | ||||
| 										<th class="text-left">{{ _("Account") }}</th> | ||||
| 										<th class="text-left">{{ _("Rate") }}</th> | ||||
| 										<th class="text-right">{{ _("Amount") }}</th> | ||||
| 									</tr> | ||||
| @ -68,14 +70,16 @@ | ||||
| 								<tbody> | ||||
| 								{% for d in data.taxes %} | ||||
| 									<tr> | ||||
| 										<td class="text-left">{{ d.account_head }}</td> | ||||
| 										<td class="text-left">{{ d.rate }} %</td> | ||||
| 										<td class='text-right'>{{ d.amount }} {{ currency.symbol }}</td> | ||||
| 										<td class='text-right'> {{ frappe.utils.fmt_money(d.amount, currency=currency) }}</td> | ||||
| 									</tr> | ||||
| 								{% endfor %} | ||||
| 							</tbody> | ||||
| 						</table> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				{% endif %} | ||||
| 				<!-- Section end --> | ||||
| 
 | ||||
| 			</div> | ||||
							
								
								
									
										149
									
								
								erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | ||||
| // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('POS Closing Entry', { | ||||
| 	onload: function(frm) { | ||||
| 		frm.set_query("pos_profile", function(doc) { | ||||
| 			return { | ||||
| 				filters: { 'user': doc.user } | ||||
| 			}; | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query("user", function(doc) { | ||||
| 			return { | ||||
| 				query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers", | ||||
| 				filters: { 'parent': doc.pos_profile } | ||||
| 			}; | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query("pos_opening_entry", function(doc) { | ||||
| 			return { filters: { 'status': 'Open', 'docstatus': 1 } }; | ||||
| 		}); | ||||
| 		 | ||||
| 		if (frm.doc.docstatus === 0) frm.set_value("period_end_date", frappe.datetime.now_datetime()); | ||||
| 		if (frm.doc.docstatus === 1) set_html_data(frm); | ||||
| 	}, | ||||
| 
 | ||||
| 	pos_opening_entry(frm) { | ||||
| 		if (frm.doc.pos_opening_entry && frm.doc.period_start_date && frm.doc.period_end_date && frm.doc.user) { | ||||
| 			reset_values(frm); | ||||
| 			frm.trigger("set_opening_amounts"); | ||||
| 			frm.trigger("get_pos_invoices"); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	set_opening_amounts(frm) { | ||||
| 		frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry) | ||||
| 			.then(({ balance_details }) => { | ||||
| 				balance_details.forEach(detail => { | ||||
| 					frm.add_child("payment_reconciliation", { | ||||
| 						mode_of_payment: detail.mode_of_payment, | ||||
| 						opening_amount: detail.opening_amount, | ||||
| 						expected_amount: detail.opening_amount | ||||
| 					}); | ||||
| 				}) | ||||
| 			}); | ||||
| 	}, | ||||
| 
 | ||||
| 	get_pos_invoices(frm) { | ||||
| 		frappe.call({ | ||||
| 			method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices', | ||||
| 			args: { | ||||
| 				start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date), | ||||
| 				end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date), | ||||
| 				user: frm.doc.user | ||||
| 			}, | ||||
| 			callback: (r) => { | ||||
| 				let pos_docs = r.message; | ||||
| 				set_form_data(pos_docs, frm) | ||||
| 				refresh_fields(frm) | ||||
| 				set_html_data(frm) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| frappe.ui.form.on('POS Closing Entry Detail', { | ||||
| 	closing_amount: (frm, cdt, cdn) => { | ||||
| 		const row = locals[cdt][cdn]; | ||||
| 		frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount)) | ||||
| 	} | ||||
| }) | ||||
| 
 | ||||
| function set_form_data(data, frm) { | ||||
| 	data.forEach(d => { | ||||
| 		add_to_pos_transaction(d, frm); | ||||
| 		frm.doc.grand_total += flt(d.grand_total); | ||||
| 		frm.doc.net_total += flt(d.net_total); | ||||
| 		frm.doc.total_quantity += flt(d.total_qty); | ||||
| 		add_to_payments(d, frm); | ||||
| 		add_to_taxes(d, frm); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function add_to_pos_transaction(d, frm) { | ||||
| 	frm.add_child("pos_transactions", { | ||||
| 		pos_invoice: d.name, | ||||
| 		posting_date: d.posting_date, | ||||
| 		grand_total: d.grand_total, | ||||
| 		customer: d.customer | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| function add_to_payments(d, frm) { | ||||
| 	d.payments.forEach(p => { | ||||
| 		const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); | ||||
| 		if (payment) { | ||||
| 			payment.expected_amount += flt(p.amount); | ||||
| 		} else { | ||||
| 			frm.add_child("payment_reconciliation", { | ||||
| 				mode_of_payment: p.mode_of_payment, | ||||
| 				opening_amount: 0, | ||||
| 				expected_amount: p.amount | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| function add_to_taxes(d, frm) { | ||||
| 	d.taxes.forEach(t => { | ||||
| 		const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate); | ||||
| 		if (tax) { | ||||
| 			tax.amount += flt(t.tax_amount);  | ||||
| 		} else { | ||||
| 			frm.add_child("taxes", { | ||||
| 				account_head: t.account_head, | ||||
| 				rate: t.rate, | ||||
| 				amount: t.tax_amount | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| function reset_values(frm) { | ||||
| 	frm.set_value("pos_transactions", []); | ||||
| 	frm.set_value("payment_reconciliation", []); | ||||
| 	frm.set_value("taxes", []); | ||||
| 	frm.set_value("grand_total", 0); | ||||
| 	frm.set_value("net_total", 0); | ||||
| 	frm.set_value("total_quantity", 0); | ||||
| } | ||||
| 
 | ||||
| function refresh_fields(frm) { | ||||
| 	frm.refresh_field("pos_transactions"); | ||||
| 	frm.refresh_field("payment_reconciliation"); | ||||
| 	frm.refresh_field("taxes"); | ||||
| 	frm.refresh_field("grand_total"); | ||||
| 	frm.refresh_field("net_total"); | ||||
| 	frm.refresh_field("total_quantity"); | ||||
| } | ||||
| 
 | ||||
| function set_html_data(frm) { | ||||
| 	frappe.call({ | ||||
| 		method: "get_payment_reconciliation_details", | ||||
| 		doc: frm.doc, | ||||
| 		callback: (r) => { | ||||
| 			frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
| @ -0,0 +1,242 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "POS-CLO-.YYYY.-.#####", | ||||
|  "creation": "2018-05-28 19:06:40.830043", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "period_start_date", | ||||
|   "period_end_date", | ||||
|   "column_break_3", | ||||
|   "posting_date", | ||||
|   "pos_opening_entry", | ||||
|   "section_break_5", | ||||
|   "company", | ||||
|   "column_break_7", | ||||
|   "pos_profile", | ||||
|   "user", | ||||
|   "section_break_12", | ||||
|   "pos_transactions", | ||||
|   "section_break_9", | ||||
|   "payment_reconciliation_details", | ||||
|   "section_break_11", | ||||
|   "payment_reconciliation", | ||||
|   "section_break_13", | ||||
|   "grand_total", | ||||
|   "net_total", | ||||
|   "total_quantity", | ||||
|   "column_break_16", | ||||
|   "taxes", | ||||
|   "section_break_14", | ||||
|   "amended_from" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fetch_from": "pos_opening_entry.period_start_date", | ||||
|    "fieldname": "period_start_date", | ||||
|    "fieldtype": "Datetime", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Period Start Date", | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "Today", | ||||
|    "fieldname": "period_end_date", | ||||
|    "fieldtype": "Datetime", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Period End Date", | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Today", | ||||
|    "fieldname": "posting_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Posting Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_5", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Company", | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_7", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "pos_opening_entry.pos_profile", | ||||
|    "fieldname": "pos_profile", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "POS Profile", | ||||
|    "options": "POS Profile", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "pos_opening_entry.user", | ||||
|    "fieldname": "user", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Cashier", | ||||
|    "options": "User", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_9", | ||||
|    "fieldtype": "Section Break", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.docstatus==1", | ||||
|    "fieldname": "payment_reconciliation_details", | ||||
|    "fieldtype": "HTML" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_11", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Modes of Payment" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "payment_reconciliation", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Payment Reconciliation", | ||||
|    "options": "POS Closing Entry Detail" | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "collapsible_depends_on": "eval:doc.docstatus==0", | ||||
|    "fieldname": "section_break_13", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Details" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "grand_total", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Grand Total", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "net_total", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Net Total", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "total_quantity", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Total Quantity", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_16", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "taxes", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Taxes", | ||||
|    "options": "POS Closing Entry Taxes", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_12", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Linked Invoices" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_14", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "amended_from", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Amended From", | ||||
|    "no_copy": 1, | ||||
|    "options": "POS Closing Entry", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "pos_transactions", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "POS Transactions", | ||||
|    "options": "POS Invoice Reference", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "pos_opening_entry", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "POS Opening Entry", | ||||
|    "options": "POS Opening Entry", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:03:22.226113", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Closing Entry", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Sales Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Administrator", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
							
								
								
									
										127
									
								
								erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| import json | ||||
| from frappe import _ | ||||
| from frappe.model.document import Document | ||||
| from frappe.utils import getdate, get_datetime, flt | ||||
| from collections import defaultdict | ||||
| from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data | ||||
| from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | ||||
| 
 | ||||
| class POSClosingEntry(Document): | ||||
| 	def validate(self): | ||||
| 		user = frappe.get_all('POS Closing Entry', | ||||
| 			filters = { 'user': self.user, 'docstatus': 1 }, | ||||
| 			or_filters = { | ||||
| 					'period_start_date': ('between', [self.period_start_date, self.period_end_date]), | ||||
| 					'period_end_date': ('between', [self.period_start_date, self.period_end_date]) | ||||
| 			}) | ||||
| 
 | ||||
| 		if user: | ||||
| 			frappe.throw(_("POS Closing Entry {} against {} between selected period" | ||||
| 				.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period")) | ||||
| 		 | ||||
| 		if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": | ||||
| 			frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		merge_pos_invoices(self.pos_transactions) | ||||
| 		opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry) | ||||
| 		opening_entry.pos_closing_entry = self.name | ||||
| 		opening_entry.set_status() | ||||
| 		opening_entry.save() | ||||
| 
 | ||||
| 	def get_payment_reconciliation_details(self): | ||||
| 		currency = frappe.get_cached_value('Company', self.company,  "default_currency") | ||||
| 		return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html", | ||||
| 			{"data": self, "currency": currency}) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_cashiers(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) | ||||
| 	return [c['user'] for c in cashiers_list] | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_pos_invoices(start, end, user): | ||||
| 	data = frappe.db.sql(""" | ||||
| 	select  | ||||
| 		name, timestamp(posting_date, posting_time) as "timestamp" | ||||
| 	from  | ||||
| 		`tabPOS Invoice` | ||||
| 	where  | ||||
| 		owner = %s and docstatus = 1 and  | ||||
| 		(consolidated_invoice is NULL or consolidated_invoice = '') | ||||
| 	""", (user), as_dict=1) | ||||
| 
 | ||||
| 	data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data)) | ||||
| 	# need to get taxes and payments so can't avoid get_doc | ||||
| 	data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data] | ||||
| 
 | ||||
| 	return data | ||||
| 
 | ||||
| def make_closing_entry_from_opening(opening_entry): | ||||
| 	closing_entry = frappe.new_doc("POS Closing Entry") | ||||
| 	closing_entry.pos_opening_entry = opening_entry.name | ||||
| 	closing_entry.period_start_date = opening_entry.period_start_date | ||||
| 	closing_entry.period_end_date = frappe.utils.get_datetime() | ||||
| 	closing_entry.pos_profile = opening_entry.pos_profile | ||||
| 	closing_entry.user = opening_entry.user | ||||
| 	closing_entry.company = opening_entry.company | ||||
| 	closing_entry.grand_total = 0 | ||||
| 	closing_entry.net_total = 0 | ||||
| 	closing_entry.total_quantity = 0 | ||||
| 
 | ||||
| 	invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user) | ||||
| 
 | ||||
| 	pos_transactions = [] | ||||
| 	taxes = [] | ||||
| 	payments = [] | ||||
| 	for detail in opening_entry.balance_details: | ||||
| 		payments.append(frappe._dict({ | ||||
| 			'mode_of_payment': detail.mode_of_payment, | ||||
| 			'opening_amount': detail.opening_amount, | ||||
| 			'expected_amount': detail.opening_amount | ||||
| 		})) | ||||
| 
 | ||||
| 	for d in invoices: | ||||
| 		pos_transactions.append(frappe._dict({ | ||||
| 			'pos_invoice': d.name, | ||||
| 			'posting_date': d.posting_date, | ||||
| 			'grand_total': d.grand_total, | ||||
| 			'customer': d.customer | ||||
| 		})) | ||||
| 		closing_entry.grand_total += flt(d.grand_total) | ||||
| 		closing_entry.net_total += flt(d.net_total) | ||||
| 		closing_entry.total_quantity += flt(d.total_qty) | ||||
| 
 | ||||
| 		for t in d.taxes: | ||||
| 			existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate] | ||||
| 			if existing_tax: | ||||
| 				existing_tax[0].amount += flt(t.tax_amount);  | ||||
| 			else: | ||||
| 				taxes.append(frappe._dict({ | ||||
| 					'account_head': t.account_head, | ||||
| 					'rate': t.rate, | ||||
| 					'amount': t.tax_amount | ||||
| 				})) | ||||
| 
 | ||||
| 		for p in d.payments: | ||||
| 			existing_pay = [pay for pay in payments if pay.mode_of_payment == p.mode_of_payment] | ||||
| 			if existing_pay: | ||||
| 				existing_pay[0].expected_amount += flt(p.amount); | ||||
| 			else: | ||||
| 				payments.append(frappe._dict({ | ||||
| 					'mode_of_payment': p.mode_of_payment, | ||||
| 					'opening_amount': 0, | ||||
| 					'expected_amount': p.amount | ||||
| 				})) | ||||
| 
 | ||||
| 	closing_entry.set("pos_transactions", pos_transactions) | ||||
| 	closing_entry.set("payment_reconciliation", payments) | ||||
| 	closing_entry.set("taxes", taxes) | ||||
| 
 | ||||
| 	return closing_entry | ||||
| @ -2,15 +2,15 @@ | ||||
| // rename this file from _test_[name] to test_[name] to activate
 | ||||
| // and remove above this line
 | ||||
| 
 | ||||
| QUnit.test("test: POS Closing Voucher", function (assert) { | ||||
| QUnit.test("test: POS Closing Entry", function (assert) { | ||||
| 	let done = assert.async(); | ||||
| 
 | ||||
| 	// number of asserts
 | ||||
| 	assert.expect(1); | ||||
| 
 | ||||
| 	frappe.run_serially([ | ||||
| 		// insert a new POS Closing Voucher
 | ||||
| 		() => frappe.tests.make('POS Closing Voucher', [ | ||||
| 		// insert a new POS Closing Entry
 | ||||
| 		() => frappe.tests.make('POS Closing Entry', [ | ||||
| 			// values to be set
 | ||||
| 			{key: 'value'} | ||||
| 		]), | ||||
| @ -0,0 +1,64 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| import unittest | ||||
| from frappe.utils import nowdate | ||||
| from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice | ||||
| from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening | ||||
| from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry | ||||
| from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile | ||||
| 
 | ||||
| class TestPOSClosingEntry(unittest.TestCase): | ||||
| 	def test_pos_closing_entry(self): | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 
 | ||||
| 		opening_entry = create_opening_entry(pos_profile, test_user.name) | ||||
| 
 | ||||
| 		pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) | ||||
| 		pos_inv1.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 | ||||
| 		}) | ||||
| 		pos_inv1.submit() | ||||
| 
 | ||||
| 		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 		pos_inv2.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 		}) | ||||
| 		pos_inv2.submit() | ||||
| 
 | ||||
| 		pcv_doc = make_closing_entry_from_opening(opening_entry) | ||||
| 		payment = pcv_doc.payment_reconciliation[0] | ||||
| 
 | ||||
| 		self.assertEqual(payment.mode_of_payment, 'Cash') | ||||
| 
 | ||||
| 		for d in pcv_doc.payment_reconciliation: | ||||
| 			if d.mode_of_payment == 'Cash': | ||||
| 				d.closing_amount = 6700 | ||||
| 
 | ||||
| 		pcv_doc.submit() | ||||
| 
 | ||||
| 		self.assertEqual(pcv_doc.total_quantity, 2) | ||||
| 		self.assertEqual(pcv_doc.net_total, 6700) | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| def init_user_and_profile(): | ||||
| 	user = 'test@example.com' | ||||
| 	test_user = frappe.get_doc('User', user) | ||||
| 
 | ||||
| 	roles = ("Accounts Manager", "Accounts User", "Sales Manager") | ||||
| 	test_user.add_roles(*roles) | ||||
| 	frappe.set_user(user) | ||||
| 
 | ||||
| 	pos_profile = make_pos_profile() | ||||
| 	pos_profile.append('applicable_for_users', { | ||||
| 		'default': 1, | ||||
| 		'user': user | ||||
| 	}) | ||||
| 
 | ||||
| 	pos_profile.save() | ||||
| 
 | ||||
| 	return test_user, pos_profile | ||||
| @ -0,0 +1,70 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2018-05-28 19:10:47.580174", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "mode_of_payment", | ||||
|   "opening_amount", | ||||
|   "closing_amount", | ||||
|   "expected_amount", | ||||
|   "difference" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "mode_of_payment", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Mode of Payment", | ||||
|    "options": "Mode of Payment", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "expected_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Expected Amount", | ||||
|    "options": "company:company_currency", | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "difference", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Difference", | ||||
|    "options": "company:company_currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "opening_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Opening Amount", | ||||
|    "options": "company:company_currency", | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "closing_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Closing Amount", | ||||
|    "options": "company:company_currency", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:03:34.533607", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Closing Entry Detail", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -5,5 +5,5 @@ | ||||
| from __future__ import unicode_literals | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class POSClosingVoucherTaxes(Document): | ||||
| class POSClosingEntryDetail(Document): | ||||
| 	pass | ||||
| @ -0,0 +1,48 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2018-05-30 09:11:22.535470", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "account_head", | ||||
|   "rate", | ||||
|   "amount" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "rate", | ||||
|    "fieldtype": "Percent", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Rate", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Amount", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "account_head", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Account Head", | ||||
|    "options": "Account", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:03:39.872884", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Closing Entry Taxes", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -5,5 +5,5 @@ | ||||
| from __future__ import unicode_literals | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class POSClosingVoucherDetails(Document): | ||||
| class POSClosingEntryTaxes(Document): | ||||
| 	pass | ||||
							
								
								
									
										205
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | ||||
| // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| {% include 'erpnext/selling/sales_common.js' %}; | ||||
| 
 | ||||
| erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({ | ||||
| 	setup(doc) { | ||||
| 		this.setup_posting_date_time_check(); | ||||
| 		this._super(doc); | ||||
| 	}, | ||||
| 
 | ||||
| 	onload() { | ||||
| 		this._super(); | ||||
| 		if(this.frm.doc.__islocal && this.frm.doc.is_pos) { | ||||
| 			//Load pos profile data on the invoice if the default value of Is POS is 1
 | ||||
| 
 | ||||
| 			me.frm.script_manager.trigger("is_pos"); | ||||
| 			me.frm.refresh_fields(); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	refresh(doc) { | ||||
| 		this._super(); | ||||
| 		if (doc.docstatus == 1 && !doc.is_return) { | ||||
| 			if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) { | ||||
| 				cur_frm.add_custom_button(__('Return'), | ||||
| 					this.make_sales_return, __('Create')); | ||||
| 				cur_frm.page.set_inner_btn_group_as_primary(__('Create')); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if (this.frm.doc.is_return) { | ||||
| 			this.frm.return_print_format = "Sales Invoice Return"; | ||||
| 			cur_frm.set_value('consolidated_invoice', ''); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	is_pos: function(frm){ | ||||
| 		this.set_pos_data(); | ||||
| 	}, | ||||
| 
 | ||||
| 	set_pos_data: function() { | ||||
| 		if(this.frm.doc.is_pos) { | ||||
| 			this.frm.set_value("allocate_advances_automatically", 0); | ||||
| 			if(!this.frm.doc.company) { | ||||
| 				this.frm.set_value("is_pos", 0); | ||||
| 				frappe.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) { | ||||
| 								me.frm.pos_print_format = r.message.print_format || ""; | ||||
| 								me.frm.meta.default_print_format = r.message.print_format || ""; | ||||
| 								me.frm.allow_edit_rate = r.message.allow_edit_rate; | ||||
| 								me.frm.allow_edit_discount = r.message.allow_edit_discount; | ||||
| 								me.frm.doc.campaign = r.message.campaign; | ||||
| 								me.frm.allow_print_before_pay = r.message.allow_print_before_pay; | ||||
| 							} | ||||
| 							me.frm.script_manager.trigger("update_stock"); | ||||
| 							me.calculate_taxes_and_totals(); | ||||
| 							if(me.frm.doc.taxes_and_charges) { | ||||
| 								me.frm.script_manager.trigger("taxes_and_charges"); | ||||
| 							} | ||||
| 							frappe.model.set_default_values(me.frm.doc); | ||||
| 							me.set_dynamic_labels(); | ||||
| 							 | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 		else this.frm.trigger("refresh"); | ||||
| 	}, | ||||
| 
 | ||||
| 	customer() { | ||||
| 		if (!this.frm.doc.customer) return | ||||
| 
 | ||||
| 		if (this.frm.doc.is_pos){ | ||||
| 			var pos_profile = this.frm.doc.pos_profile; | ||||
| 		} | ||||
| 		var me = this; | ||||
| 		if(this.frm.updating_party_details) return; | ||||
| 		erpnext.utils.get_party_details(this.frm, | ||||
| 			"erpnext.accounts.party.get_party_details", { | ||||
| 				posting_date: this.frm.doc.posting_date, | ||||
| 				party: this.frm.doc.customer, | ||||
| 				party_type: "Customer", | ||||
| 				account: this.frm.doc.debit_to, | ||||
| 				price_list: this.frm.doc.selling_price_list, | ||||
| 				pos_profile: pos_profile | ||||
| 			}, function() { | ||||
| 				me.apply_pricing_rule(); | ||||
| 			}); | ||||
| 	}, | ||||
| 
 | ||||
| 	amount: function(){ | ||||
| 		this.write_off_outstanding_amount_automatically() | ||||
| 	}, | ||||
| 
 | ||||
| 	change_amount: function(){ | ||||
| 		if(this.frm.doc.paid_amount > this.frm.doc.grand_total){ | ||||
| 			this.calculate_write_off_amount(); | ||||
| 		}else { | ||||
| 			this.frm.set_value("change_amount", 0.0); | ||||
| 			this.frm.set_value("base_change_amount", 0.0); | ||||
| 		} | ||||
| 
 | ||||
| 		this.frm.refresh_fields(); | ||||
| 	}, | ||||
| 
 | ||||
| 	loyalty_amount: function(){ | ||||
| 		this.calculate_outstanding_amount(); | ||||
| 		this.frm.refresh_field("outstanding_amount"); | ||||
| 		this.frm.refresh_field("paid_amount"); | ||||
| 		this.frm.refresh_field("base_paid_amount"); | ||||
| 	}, | ||||
| 
 | ||||
| 	write_off_outstanding_amount_automatically: function() { | ||||
| 		if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) { | ||||
| 			frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]); | ||||
| 			// this will make outstanding amount 0
 | ||||
| 			this.frm.set_value("write_off_amount", | ||||
| 				flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount")) | ||||
| 			); | ||||
| 			this.frm.toggle_enable("write_off_amount", false); | ||||
| 
 | ||||
| 		} else { | ||||
| 			this.frm.toggle_enable("write_off_amount", true); | ||||
| 		} | ||||
| 
 | ||||
| 		this.calculate_outstanding_amount(false); | ||||
| 		this.frm.refresh_fields(); | ||||
| 	}, | ||||
| 
 | ||||
| 	make_sales_return: function() { | ||||
| 		frappe.model.open_mapped_doc({ | ||||
| 			method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return", | ||||
| 			frm: cur_frm | ||||
| 		}) | ||||
| 	}, | ||||
| }) | ||||
| 
 | ||||
| $.extend(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm })) | ||||
| 
 | ||||
| frappe.ui.form.on('POS Invoice', { | ||||
| 	redeem_loyalty_points: function(frm) { | ||||
| 		frm.events.get_loyalty_details(frm); | ||||
| 	}, | ||||
| 
 | ||||
| 	loyalty_points: function(frm) { | ||||
| 		if (frm.redemption_conversion_factor) { | ||||
| 			frm.events.set_loyalty_points(frm); | ||||
| 		} else { | ||||
| 			frappe.call({ | ||||
| 				method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", | ||||
| 				args: { | ||||
| 					"loyalty_program": frm.doc.loyalty_program | ||||
| 				}, | ||||
| 				callback: function(r) { | ||||
| 					if (r) { | ||||
| 						frm.redemption_conversion_factor = r.message; | ||||
| 						frm.events.set_loyalty_points(frm); | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	get_loyalty_details: function(frm) { | ||||
| 		if (frm.doc.customer && frm.doc.redeem_loyalty_points) { | ||||
| 			frappe.call({ | ||||
| 				method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", | ||||
| 				args: { | ||||
| 					"customer": frm.doc.customer, | ||||
| 					"loyalty_program": frm.doc.loyalty_program, | ||||
| 					"expiry_date": frm.doc.posting_date, | ||||
| 					"company": frm.doc.company | ||||
| 				}, | ||||
| 				callback: function(r) { | ||||
| 					if (r) { | ||||
| 						frm.set_value("loyalty_redemption_account", r.message.expense_account); | ||||
| 						frm.set_value("loyalty_redemption_cost_center", r.message.cost_center); | ||||
| 						frm.redemption_conversion_factor = r.message.conversion_factor; | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	set_loyalty_points: function(frm) { | ||||
| 		if (frm.redemption_conversion_factor) { | ||||
| 			let loyalty_amount = flt(frm.redemption_conversion_factor*flt(frm.doc.loyalty_points), precision("loyalty_amount")); | ||||
| 			var remaining_amount = flt(frm.doc.grand_total) - flt(frm.doc.total_advance) - flt(frm.doc.write_off_amount); | ||||
| 			if (frm.doc.grand_total && (remaining_amount < loyalty_amount)) { | ||||
| 				let redeemable_points = parseInt(remaining_amount/frm.redemption_conversion_factor); | ||||
| 				frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_points])); | ||||
| 			} | ||||
| 			frm.set_value("loyalty_amount", loyalty_amount); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										1637
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1637
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										374
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,374 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.model.document import Document | ||||
| from erpnext.controllers.selling_controller import SellingController | ||||
| from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate | ||||
| from erpnext.accounts.utils import get_account_currency | ||||
| from erpnext.accounts.party import get_party_account, get_due_date | ||||
| from erpnext.accounts.doctype.loyalty_program.loyalty_program import \ | ||||
| 	get_loyalty_program_details_with_points, validate_loyalty_points | ||||
| 
 | ||||
| from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option | ||||
| from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos | ||||
| 
 | ||||
| from six import iteritems | ||||
| 
 | ||||
| class POSInvoice(SalesInvoice): | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		super(POSInvoice, self).__init__(*args, **kwargs) | ||||
| 	 | ||||
| 	def validate(self): | ||||
| 		if not cint(self.is_pos): | ||||
| 			frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment"))) | ||||
| 
 | ||||
| 		# run on validate method of selling controller | ||||
| 		super(SalesInvoice, self).validate() | ||||
| 		self.validate_auto_set_posting_time() | ||||
| 		self.validate_pos_paid_amount() | ||||
| 		self.validate_pos_return() | ||||
| 		self.validate_uom_is_integer("stock_uom", "stock_qty") | ||||
| 		self.validate_uom_is_integer("uom", "qty") | ||||
| 		self.validate_debit_to_acc() | ||||
| 		self.validate_write_off_account() | ||||
| 		self.validate_change_amount() | ||||
| 		self.validate_change_account() | ||||
| 		self.validate_item_cost_centers() | ||||
| 		self.validate_serialised_or_batched_item() | ||||
| 		self.validate_stock_availablility() | ||||
| 		self.validate_return_items() | ||||
| 		self.set_status() | ||||
| 		self.set_account_for_mode_of_payment() | ||||
| 		self.validate_pos() | ||||
| 		self.verify_payment_amount() | ||||
| 		self.validate_loyalty_transaction() | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		# create the loyalty point ledger entry if the customer is enrolled in any loyalty program | ||||
| 		if self.loyalty_program: | ||||
| 			self.make_loyalty_point_entry() | ||||
| 		elif self.is_return and self.return_against and self.loyalty_program: | ||||
| 			against_psi_doc = frappe.get_doc("POS Invoice", self.return_against) | ||||
| 			against_psi_doc.delete_loyalty_point_entry() | ||||
| 			against_psi_doc.make_loyalty_point_entry() | ||||
| 		if self.redeem_loyalty_points and self.loyalty_points: | ||||
| 			self.apply_loyalty_points() | ||||
| 		self.set_status(update=True) | ||||
| 	 | ||||
| 	def on_cancel(self): | ||||
| 		# run on cancel method of selling controller | ||||
| 		super(SalesInvoice, self).on_cancel() | ||||
| 		if self.loyalty_program: | ||||
| 			self.delete_loyalty_point_entry() | ||||
| 		elif self.is_return and self.return_against and self.loyalty_program: | ||||
| 			against_psi_doc = frappe.get_doc("POS Invoice", self.return_against) | ||||
| 			against_psi_doc.delete_loyalty_point_entry() | ||||
| 			against_psi_doc.make_loyalty_point_entry() | ||||
| 		 | ||||
| 	def validate_stock_availablility(self): | ||||
| 		allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') | ||||
| 		 | ||||
| 		for d in self.get('items'): | ||||
| 			if d.serial_no: | ||||
| 				filters = { | ||||
| 					"item_code": d.item_code, | ||||
| 					"warehouse": d.warehouse, | ||||
| 					"delivery_document_no": "", | ||||
| 					"sales_invoice": "" | ||||
| 				} | ||||
| 				if d.batch_no: | ||||
| 					filters["batch_no"] = d.batch_no | ||||
| 				reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters) | ||||
| 				serial_nos = d.serial_no.split("\n") | ||||
| 				serial_nos = ' '.join(serial_nos).split() # remove whitespaces | ||||
| 				invalid_serial_nos = [] | ||||
| 				for s in serial_nos: | ||||
| 					if s in reserved_serial_nos: | ||||
| 						invalid_serial_nos.append(s) | ||||
| 				 | ||||
| 				if len(invalid_serial_nos): | ||||
| 					multiple_nos = 's' if len(invalid_serial_nos) > 1 else '' | ||||
| 					frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \ | ||||
| 						Please select valid serial no.".format(d.idx, multiple_nos,  | ||||
| 						frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available")) | ||||
| 			else: | ||||
| 				if allow_negative_stock: | ||||
| 					return | ||||
| 
 | ||||
| 				available_stock = get_stock_availability(d.item_code, d.warehouse) | ||||
| 				if not (flt(available_stock) > 0): | ||||
| 					frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.' | ||||
| 						.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available")) | ||||
| 				elif flt(available_stock) < flt(d.qty): | ||||
| 					frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \ | ||||
| 						Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),  | ||||
| 						frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available")) | ||||
| 	 | ||||
| 	def validate_serialised_or_batched_item(self): | ||||
| 		for d in self.get("items"): | ||||
| 			serialized = d.get("has_serial_no") | ||||
| 			batched = d.get("has_batch_no") | ||||
| 			no_serial_selected = not d.get("serial_no") | ||||
| 			no_batch_selected = not d.get("batch_no") | ||||
| 
 | ||||
| 
 | ||||
| 			if serialized and batched and (no_batch_selected or no_serial_selected): | ||||
| 				frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.' | ||||
| 						.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) | ||||
| 			if serialized and no_serial_selected: | ||||
| 				frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.' | ||||
| 						.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) | ||||
| 			if batched and no_batch_selected: | ||||
| 				frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.' | ||||
| 						.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) | ||||
| 	 | ||||
| 	def validate_return_items(self): | ||||
| 		if not self.get("is_return"): return | ||||
| 
 | ||||
| 		for d in self.get("items"): | ||||
| 			if d.get("qty") > 0: | ||||
| 				frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.") | ||||
| 					.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) | ||||
| 
 | ||||
| 	def validate_pos_paid_amount(self): | ||||
| 		if len(self.payments) == 0 and self.is_pos: | ||||
| 			frappe.throw(_("At least one mode of payment is required for POS invoice.")) | ||||
| 
 | ||||
| 	def validate_change_account(self): | ||||
| 		if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company: | ||||
| 			frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company)) | ||||
| 
 | ||||
| 	def validate_change_amount(self): | ||||
| 		grand_total = flt(self.rounded_total) or flt(self.grand_total) | ||||
| 		base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total) | ||||
| 		if not flt(self.change_amount) and grand_total < flt(self.paid_amount): | ||||
| 			self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount)) | ||||
| 			self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount)) | ||||
| 
 | ||||
| 		if flt(self.change_amount) and not self.account_for_change_amount: | ||||
| 			msgprint(_("Please enter Account for Change Amount"), raise_exception=1) | ||||
| 
 | ||||
| 	def verify_payment_amount(self): | ||||
| 		for entry in self.payments: | ||||
| 			if not self.is_return and entry.amount < 0: | ||||
| 				frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx)) | ||||
| 			if self.is_return and entry.amount > 0: | ||||
| 				frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx)) | ||||
| 	 | ||||
| 	def validate_pos_return(self): | ||||
| 		if self.is_pos and self.is_return: | ||||
| 			total_amount_in_payments = 0 | ||||
| 			for payment in self.payments: | ||||
| 				total_amount_in_payments += payment.amount | ||||
| 			invoice_total = self.rounded_total or self.grand_total | ||||
| 			if total_amount_in_payments < invoice_total: | ||||
| 				frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) | ||||
| 	 | ||||
| 	def validate_loyalty_transaction(self): | ||||
| 		if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center): | ||||
| 			expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"]) | ||||
| 			if not self.loyalty_redemption_account: | ||||
| 				self.loyalty_redemption_account = expense_account  | ||||
| 			if not self.loyalty_redemption_cost_center: | ||||
| 				self.loyalty_redemption_cost_center = cost_center | ||||
| 
 | ||||
| 		if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: | ||||
| 			validate_loyalty_points(self, self.loyalty_points) | ||||
| 
 | ||||
| 	def set_status(self, update=False, status=None, update_modified=True): | ||||
| 		if self.is_new(): | ||||
| 			if self.get('amended_from'): | ||||
| 				self.status = 'Draft' | ||||
| 			return | ||||
| 
 | ||||
| 		if not status: | ||||
| 			if self.docstatus == 2: | ||||
| 				status = "Cancelled" | ||||
| 			elif self.docstatus == 1: | ||||
| 				if self.consolidated_invoice: | ||||
| 					self.status = "Consolidated" | ||||
| 				elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed': | ||||
| 					self.status = "Overdue and Discounted" | ||||
| 				elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()): | ||||
| 					self.status = "Overdue" | ||||
| 				elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed': | ||||
| 					self.status = "Unpaid and Discounted" | ||||
| 				elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()): | ||||
| 					self.status = "Unpaid" | ||||
| 				elif flt(self.outstanding_amount) <= 0 and self.is_return == 0 and frappe.db.get_value('POS Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): | ||||
| 					self.status = "Credit Note Issued" | ||||
| 				elif self.is_return == 1: | ||||
| 					self.status = "Return" | ||||
| 				elif flt(self.outstanding_amount)<=0: | ||||
| 					self.status = "Paid" | ||||
| 				else: | ||||
| 					self.status = "Submitted" | ||||
| 			else: | ||||
| 				self.status = "Draft" | ||||
| 
 | ||||
| 		if update: | ||||
| 			self.db_set('status', self.status, update_modified = update_modified) | ||||
| 	 | ||||
| 	def set_pos_fields(self, for_validate=False): | ||||
| 		"""Set retail related fields from POS Profiles""" | ||||
| 		from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile | ||||
| 		if not self.pos_profile: | ||||
| 			pos_profile = get_pos_profile(self.company) or {} | ||||
| 			self.pos_profile = pos_profile.get('name') | ||||
| 
 | ||||
| 		pos = {} | ||||
| 		if self.pos_profile: | ||||
| 			pos = frappe.get_doc('POS Profile', self.pos_profile) | ||||
| 
 | ||||
| 		if not self.get('payments') and not for_validate: | ||||
| 			update_multi_mode_option(self, pos) | ||||
| 
 | ||||
| 		if not self.account_for_change_amount: | ||||
| 			self.account_for_change_amount = frappe.get_cached_value('Company',  self.company,  'default_cash_account') | ||||
| 
 | ||||
| 		if pos: | ||||
| 			if not for_validate: | ||||
| 				self.tax_category = pos.get("tax_category") | ||||
| 
 | ||||
| 			if not for_validate and not self.customer: | ||||
| 				self.customer = pos.customer | ||||
| 
 | ||||
| 			self.ignore_pricing_rule = pos.ignore_pricing_rule | ||||
| 			if pos.get('account_for_change_amount'): | ||||
| 				self.account_for_change_amount = pos.get('account_for_change_amount') | ||||
| 			if pos.get('warehouse'): | ||||
| 				self.set_warehouse = pos.get('warehouse') | ||||
| 
 | ||||
| 			for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name', | ||||
| 				'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', | ||||
| 				'write_off_cost_center', 'apply_discount_on', 'cost_center'): | ||||
| 					if (not for_validate) or (for_validate and not self.get(fieldname)): | ||||
| 						self.set(fieldname, pos.get(fieldname)) | ||||
| 
 | ||||
| 			if pos.get("company_address"): | ||||
| 				self.company_address = pos.get("company_address") | ||||
| 
 | ||||
| 			if self.customer: | ||||
| 				customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) | ||||
| 				customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list') | ||||
| 				selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list') | ||||
| 			else: | ||||
| 				selling_price_list = pos.get('selling_price_list') | ||||
| 
 | ||||
| 			if selling_price_list: | ||||
| 				self.set('selling_price_list', selling_price_list) | ||||
| 
 | ||||
| 			if not for_validate: | ||||
| 				self.update_stock = cint(pos.get("update_stock")) | ||||
| 
 | ||||
| 			# set pos values in items | ||||
| 			for item in self.get("items"): | ||||
| 				if item.get('item_code'): | ||||
| 					profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos) | ||||
| 					for fname, val in iteritems(profile_details): | ||||
| 						if (not for_validate) or (for_validate and not item.get(fname)): | ||||
| 							item.set(fname, val) | ||||
| 
 | ||||
| 			# fetch terms | ||||
| 			if self.tc_name and not self.terms: | ||||
| 				self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms") | ||||
| 
 | ||||
| 			# fetch charges | ||||
| 			if self.taxes_and_charges and not len(self.get("taxes")): | ||||
| 				self.set_taxes() | ||||
| 
 | ||||
| 		return pos | ||||
| 
 | ||||
| 	def set_missing_values(self, for_validate=False): | ||||
| 		pos = self.set_pos_fields(for_validate) | ||||
| 
 | ||||
| 		if not self.debit_to: | ||||
| 			self.debit_to = get_party_account("Customer", self.customer, self.company) | ||||
| 			self.party_account_currency = frappe.db.get_value("Account", self.debit_to, "account_currency", cache=True) | ||||
| 		if not self.due_date and self.customer: | ||||
| 			self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company) | ||||
| 
 | ||||
| 		super(SalesInvoice, self).set_missing_values(for_validate) | ||||
| 
 | ||||
| 		print_format = pos.get("print_format") if pos else None | ||||
| 		if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')): | ||||
| 			print_format = 'POS Invoice' | ||||
| 
 | ||||
| 		if pos: | ||||
| 			return { | ||||
| 				"print_format": print_format, | ||||
| 				"allow_edit_rate": pos.get("allow_user_to_edit_rate"), | ||||
| 				"allow_edit_discount": pos.get("allow_user_to_edit_discount"), | ||||
| 				"campaign": pos.get("campaign"), | ||||
| 				"allow_print_before_pay": pos.get("allow_print_before_pay") | ||||
| 			} | ||||
| 
 | ||||
| 	def set_account_for_mode_of_payment(self): | ||||
| 		self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default] | ||||
| 		for pay in self.payments: | ||||
| 			if not pay.account: | ||||
| 				pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account") | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_stock_availability(item_code, warehouse): | ||||
| 	latest_sle = frappe.db.sql("""select qty_after_transaction  | ||||
| 		from `tabStock Ledger Entry`  | ||||
| 		where item_code = %s and warehouse = %s | ||||
| 		order by posting_date desc, posting_time desc | ||||
| 		limit 1""", (item_code, warehouse), as_dict=1) | ||||
| 	 | ||||
| 	pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty | ||||
| 		from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item | ||||
| 		where p.name = p_item.parent  | ||||
| 		and p.consolidated_invoice is NULL  | ||||
| 		and p.docstatus = 1 | ||||
| 		and p_item.docstatus = 1 | ||||
| 		and p_item.item_code = %s | ||||
| 		and p_item.warehouse = %s | ||||
| 		""", (item_code, warehouse), as_dict=1) | ||||
| 	 | ||||
| 	sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 | ||||
| 	pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0 | ||||
| 	 | ||||
| 	if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty: | ||||
| 		return sle_qty - pos_sales_qty | ||||
| 	else: | ||||
| 		# when sle_qty is 0 | ||||
| 		# when sle_qty > 0 and pos_sales_qty is 0 | ||||
| 		return sle_qty | ||||
| 
 | ||||
| @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("POS Invoice", source_name, target_doc) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def make_merge_log(invoices): | ||||
| 	import json | ||||
| 	from six import string_types | ||||
| 
 | ||||
| 	if isinstance(invoices, string_types): | ||||
| 		invoices = json.loads(invoices) | ||||
| 
 | ||||
| 	if len(invoices) == 0: | ||||
| 		frappe.throw(_('Atleast one invoice has to be selected.')) | ||||
| 
 | ||||
| 	merge_log = frappe.new_doc("POS Invoice Merge Log") | ||||
| 	merge_log.posting_date = getdate(nowdate()) | ||||
| 	for inv in invoices: | ||||
| 		inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),  | ||||
| 			["customer", "posting_date", "grand_total"], as_dict=1)[0] | ||||
| 		merge_log.customer = inv_data.customer | ||||
| 		merge_log.append("pos_invoices", { | ||||
| 			'pos_invoice': inv.get('name'), | ||||
| 			'customer': inv_data.customer, | ||||
| 			'posting_date': inv_data.posting_date, | ||||
| 			'grand_total': inv_data.grand_total  | ||||
| 		}) | ||||
| 
 | ||||
| 	if merge_log.get('pos_invoices'): | ||||
| 		return merge_log.as_dict() | ||||
							
								
								
									
										42
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 | ||||
| // License: GNU General Public License v3. See license.txt
 | ||||
| 
 | ||||
| // render
 | ||||
| frappe.listview_settings['POS Invoice'] = { | ||||
| 	add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", | ||||
| 		"currency", "is_return"], | ||||
| 	get_indicator: function(doc) { | ||||
| 		var status_color = { | ||||
| 			"Draft": "red", | ||||
| 			"Unpaid": "orange", | ||||
| 			"Paid": "green", | ||||
| 			"Submitted": "blue", | ||||
| 			"Consolidated": "green", | ||||
| 			"Return": "darkgrey", | ||||
| 			"Unpaid and Discounted": "orange", | ||||
| 			"Overdue and Discounted": "red", | ||||
| 			"Overdue": "red" | ||||
| 
 | ||||
| 		}; | ||||
| 		return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; | ||||
| 	}, | ||||
| 	right_column: "grand_total", | ||||
| 	onload: function(me) { | ||||
| 		me.page.add_action_item('Make Merge Log', function() { | ||||
| 			const invoices = me.get_checked_items(); | ||||
| 			frappe.call({ | ||||
| 				method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_merge_log", | ||||
| 				freeze: true, | ||||
| 				args:{ | ||||
| 					"invoices": invoices | ||||
| 				}, | ||||
| 				callback: function (r) { | ||||
| 					if (r.message) { | ||||
| 						var doc = frappe.model.sync(r.message)[0]; | ||||
| 						frappe.set_route("Form", doc.doctype, doc.name); | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										334
									
								
								erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,334 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest, copy, time | ||||
| from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile | ||||
| from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return | ||||
| 
 | ||||
| class TestPOSInvoice(unittest.TestCase): | ||||
| 	def test_timestamp_change(self): | ||||
| 		w = create_pos_invoice(do_not_save=1) | ||||
| 		w.docstatus = 0 | ||||
| 		w.insert() | ||||
| 
 | ||||
| 		w2 = frappe.get_doc(w.doctype, w.name) | ||||
| 
 | ||||
| 		import time | ||||
| 		time.sleep(1) | ||||
| 		w.save() | ||||
| 
 | ||||
| 		import time | ||||
| 		time.sleep(1) | ||||
| 		self.assertRaises(frappe.TimestampMismatchError, w2.save) | ||||
| 
 | ||||
| 	def test_change_naming_series(self): | ||||
| 		inv = create_pos_invoice(do_not_submit=1) | ||||
| 		inv.naming_series = 'TEST-' | ||||
| 
 | ||||
| 		self.assertRaises(frappe.CannotChangeConstantError, inv.save) | ||||
| 
 | ||||
| 	def test_discount_and_inclusive_tax(self): | ||||
| 		inv = create_pos_invoice(qty=100, rate=50, do_not_save=1) | ||||
| 		inv.append("taxes", { | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"account_head": "_Test Account Service Tax - _TC", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "Service Tax", | ||||
| 			"rate": 14, | ||||
| 			'included_in_print_rate': 1 | ||||
| 		}) | ||||
| 		inv.insert() | ||||
| 
 | ||||
| 		self.assertEqual(inv.net_total, 4385.96) | ||||
| 		self.assertEqual(inv.grand_total, 5000) | ||||
| 
 | ||||
| 		inv.reload() | ||||
| 
 | ||||
| 		inv.discount_amount = 100 | ||||
| 		inv.apply_discount_on = 'Net Total' | ||||
| 		inv.payment_schedule = [] | ||||
| 
 | ||||
| 		inv.save() | ||||
| 
 | ||||
| 		self.assertEqual(inv.net_total, 4285.96) | ||||
| 		self.assertEqual(inv.grand_total, 4885.99) | ||||
| 
 | ||||
| 		inv.reload() | ||||
| 
 | ||||
| 		inv.discount_amount = 100 | ||||
| 		inv.apply_discount_on = 'Grand Total' | ||||
| 		inv.payment_schedule = [] | ||||
| 
 | ||||
| 		inv.save() | ||||
| 
 | ||||
| 		self.assertEqual(inv.net_total, 4298.25) | ||||
| 		self.assertEqual(inv.grand_total, 4900.00) | ||||
| 
 | ||||
| 	def test_tax_calculation_with_multiple_items(self): | ||||
| 		inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True) | ||||
| 		item_row = inv.get("items")[0] | ||||
| 		for qty in (54, 288, 144, 430): | ||||
| 			item_row_copy = copy.deepcopy(item_row) | ||||
| 			item_row_copy.qty = qty | ||||
| 			inv.append("items", item_row_copy) | ||||
| 
 | ||||
| 		inv.append("taxes", { | ||||
| 			"account_head": "_Test Account VAT - _TC", | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "VAT", | ||||
| 			"doctype": "Sales Taxes and Charges", | ||||
| 			"rate": 19 | ||||
| 		}) | ||||
| 		inv.insert() | ||||
| 
 | ||||
| 		self.assertEqual(inv.net_total, 4600) | ||||
| 
 | ||||
| 		self.assertEqual(inv.get("taxes")[0].tax_amount, 874.0) | ||||
| 		self.assertEqual(inv.get("taxes")[0].total, 5474.0) | ||||
| 
 | ||||
| 		self.assertEqual(inv.grand_total, 5474.0) | ||||
| 
 | ||||
| 	def test_tax_calculation_with_item_tax_template(self): | ||||
| 		inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1) | ||||
| 		item_row = inv.get("items")[0] | ||||
| 
 | ||||
| 		add_items = [ | ||||
| 			(54, '_Test Account Excise Duty @ 12'), | ||||
| 			(288, '_Test Account Excise Duty @ 15'), | ||||
| 			(144, '_Test Account Excise Duty @ 20'), | ||||
| 			(430, '_Test Item Tax Template 1') | ||||
| 		] | ||||
| 		for qty, item_tax_template in add_items: | ||||
| 			item_row_copy = copy.deepcopy(item_row) | ||||
| 			item_row_copy.qty = qty | ||||
| 			item_row_copy.item_tax_template = item_tax_template | ||||
| 			inv.append("items", item_row_copy) | ||||
| 
 | ||||
| 		inv.append("taxes", { | ||||
| 			"account_head": "_Test Account Excise Duty - _TC", | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "Excise Duty", | ||||
| 			"doctype": "Sales Taxes and Charges", | ||||
| 			"rate": 11 | ||||
| 		}) | ||||
| 		inv.append("taxes", { | ||||
| 			"account_head": "_Test Account Education Cess - _TC", | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "Education Cess", | ||||
| 			"doctype": "Sales Taxes and Charges", | ||||
| 			"rate": 0 | ||||
| 		}) | ||||
| 		inv.append("taxes", { | ||||
| 			"account_head": "_Test Account S&H Education Cess - _TC", | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "S&H Education Cess", | ||||
| 			"doctype": "Sales Taxes and Charges", | ||||
| 			"rate": 3 | ||||
| 		}) | ||||
| 		inv.insert() | ||||
| 
 | ||||
| 		self.assertEqual(inv.net_total, 4600) | ||||
| 
 | ||||
| 		self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41) | ||||
| 		self.assertEqual(inv.get("taxes")[0].total, 5102.41) | ||||
| 
 | ||||
| 		self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80) | ||||
| 		self.assertEqual(inv.get("taxes")[1].total, 5300.21) | ||||
| 
 | ||||
| 		self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36) | ||||
| 		self.assertEqual(inv.get("taxes")[2].total, 5675.57) | ||||
| 
 | ||||
| 		self.assertEqual(inv.grand_total, 5675.57) | ||||
| 		self.assertEqual(inv.rounding_adjustment, 0.43) | ||||
| 		self.assertEqual(inv.rounded_total, 5676.0) | ||||
| 
 | ||||
| 	def test_tax_calculation_with_multiple_items_and_discount(self): | ||||
| 		inv = create_pos_invoice(qty=1, rate=75, do_not_save=True) | ||||
| 		item_row = inv.get("items")[0] | ||||
| 		for rate in (500, 200, 100, 50, 50): | ||||
| 			item_row_copy = copy.deepcopy(item_row) | ||||
| 			item_row_copy.price_list_rate = rate | ||||
| 			item_row_copy.rate = rate | ||||
| 			inv.append("items", item_row_copy) | ||||
| 
 | ||||
| 		inv.apply_discount_on = "Net Total" | ||||
| 		inv.discount_amount = 75.0 | ||||
| 
 | ||||
| 		inv.append("taxes", { | ||||
| 			"account_head": "_Test Account VAT - _TC", | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "VAT", | ||||
| 			"doctype": "Sales Taxes and Charges", | ||||
| 			"rate": 24 | ||||
| 		}) | ||||
| 		inv.insert() | ||||
| 
 | ||||
| 		self.assertEqual(inv.total, 975) | ||||
| 		self.assertEqual(inv.net_total, 900) | ||||
| 
 | ||||
| 		self.assertEqual(inv.get("taxes")[0].tax_amount, 216.0) | ||||
| 		self.assertEqual(inv.get("taxes")[0].total, 1116.0) | ||||
| 
 | ||||
| 		self.assertEqual(inv.grand_total, 1116.0) | ||||
| 
 | ||||
| 	def test_pos_returns_with_repayment(self): | ||||
| 		pos = create_pos_invoice(qty = 10, do_not_save=True) | ||||
| 
 | ||||
| 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500}) | ||||
| 		pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500}) | ||||
| 		pos.insert() | ||||
| 		pos.submit() | ||||
| 
 | ||||
| 		pos_return = make_sales_return(pos.name) | ||||
| 
 | ||||
| 		pos_return.insert() | ||||
| 		pos_return.submit() | ||||
| 
 | ||||
| 		self.assertEqual(pos_return.get('payments')[0].amount, -500) | ||||
| 		self.assertEqual(pos_return.get('payments')[1].amount, -500) | ||||
| 
 | ||||
| 	def test_pos_change_amount(self): | ||||
| 		pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC", | ||||
| 			income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105, | ||||
| 			cost_center = "Main - _TC", do_not_save=True) | ||||
| 
 | ||||
| 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50}) | ||||
| 		pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60}) | ||||
| 
 | ||||
| 		pos.insert() | ||||
| 		pos.submit() | ||||
| 
 | ||||
| 		self.assertEqual(pos.grand_total, 105.0) | ||||
| 		self.assertEqual(pos.change_amount, 5.0) | ||||
| 
 | ||||
| 	def test_without_payment(self): | ||||
| 		inv = create_pos_invoice(do_not_save=1) | ||||
| 		# Check that the invoice cannot be submitted without payments | ||||
| 		inv.payments = [] | ||||
| 		self.assertRaises(frappe.ValidationError, inv.insert) | ||||
| 
 | ||||
| 	def test_serialized_item_transaction(self): | ||||
| 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item | ||||
| 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos | ||||
| 
 | ||||
| 		se = make_serialized_item(company='_Test Company with perpetual inventory', | ||||
| 			target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1') | ||||
| 
 | ||||
| 		serial_nos = get_serial_nos(se.get("items")[0].serial_no) | ||||
| 
 | ||||
| 		pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', | ||||
| 			account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', | ||||
| 			expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', | ||||
| 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1) | ||||
| 
 | ||||
| 		pos.get("items")[0].serial_no = serial_nos[0] | ||||
| 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) | ||||
| 
 | ||||
| 		pos.insert() | ||||
| 		pos.submit() | ||||
| 
 | ||||
| 		pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', | ||||
| 			account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', | ||||
| 			expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', | ||||
| 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1) | ||||
| 
 | ||||
| 		pos2.get("items")[0].serial_no = serial_nos[0] | ||||
| 		pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) | ||||
| 
 | ||||
| 		self.assertRaises(frappe.ValidationError, pos2.insert) | ||||
| 
 | ||||
| 	def test_loyalty_points(self): | ||||
| 		from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records | ||||
| 		from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points | ||||
| 
 | ||||
| 		create_records() | ||||
| 		frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty") | ||||
| 		before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty") | ||||
| 
 | ||||
| 		inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000) | ||||
| 
 | ||||
| 		lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'POS Invoice', 'invoice': inv.name, 'customer': inv.customer}) | ||||
| 		after_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) | ||||
| 
 | ||||
| 		self.assertEqual(inv.get('loyalty_program'), "Test Single Loyalty") | ||||
| 		self.assertEqual(lpe.loyalty_points, 10) | ||||
| 		self.assertEqual(after_lp_details.loyalty_points, before_lp_details.loyalty_points + 10) | ||||
| 
 | ||||
| 		inv.cancel() | ||||
| 		after_cancel_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) | ||||
| 		self.assertEqual(after_cancel_lp_details.loyalty_points, before_lp_details.loyalty_points) | ||||
| 
 | ||||
| 	def test_loyalty_points_redeemption(self): | ||||
| 		from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points | ||||
| 		# add 10 loyalty points | ||||
| 		create_pos_invoice(customer="Test Loyalty Customer", rate=10000) | ||||
| 
 | ||||
| 		before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty") | ||||
| 
 | ||||
| 		inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1) | ||||
| 		inv.redeem_loyalty_points = 1 | ||||
| 		inv.loyalty_points = before_lp_details.loyalty_points | ||||
| 		inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor | ||||
| 		inv.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 10000 - inv.loyalty_amount}) | ||||
| 		inv.paid_amount = 10000 | ||||
| 		inv.submit() | ||||
| 
 | ||||
| 		after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) | ||||
| 		self.assertEqual(after_redeem_lp_details.loyalty_points, 9) | ||||
| 
 | ||||
| def create_pos_invoice(**args): | ||||
| 	args = frappe._dict(args) | ||||
| 	pos_profile = None | ||||
| 	if not args.pos_profile: | ||||
| 		pos_profile = make_pos_profile() | ||||
| 		pos_profile.save() | ||||
| 
 | ||||
| 	pos_inv = frappe.new_doc("POS Invoice") | ||||
| 	pos_inv.update_stock = 1 | ||||
| 	pos_inv.is_pos = 1 | ||||
| 	pos_inv.pos_profile = args.pos_profile or pos_profile.name | ||||
| 
 | ||||
| 	pos_inv.set_missing_values() | ||||
| 
 | ||||
| 	if args.posting_date: | ||||
| 		pos_inv.set_posting_time = 1 | ||||
| 	pos_inv.posting_date = args.posting_date or frappe.utils.nowdate() | ||||
| 
 | ||||
| 	pos_inv.company = args.company or "_Test Company" | ||||
| 	pos_inv.customer = args.customer or "_Test Customer" | ||||
| 	pos_inv.debit_to = args.debit_to or "Debtors - _TC" | ||||
| 	pos_inv.is_return = args.is_return | ||||
| 	pos_inv.return_against = args.return_against | ||||
| 	pos_inv.currency=args.currency or "INR" | ||||
| 	pos_inv.conversion_rate = args.conversion_rate or 1 | ||||
| 	pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC" | ||||
| 
 | ||||
| 	pos_inv.append("items", { | ||||
| 		"item_code": args.item or args.item_code or "_Test Item", | ||||
| 		"warehouse": args.warehouse or "_Test Warehouse - _TC", | ||||
| 		"qty": args.qty or 1, | ||||
| 		"rate": args.rate if args.get("rate") is not None else 100, | ||||
| 		"income_account": args.income_account or "Sales - _TC", | ||||
| 		"expense_account": args.expense_account or "Cost of Goods Sold - _TC", | ||||
| 		"cost_center": args.cost_center or "_Test Cost Center - _TC", | ||||
| 		"serial_no": args.serial_no | ||||
| 	}) | ||||
| 
 | ||||
| 	if not args.do_not_save: | ||||
| 		pos_inv.insert() | ||||
| 		if not args.do_not_submit: | ||||
| 			pos_inv.submit() | ||||
| 		else: | ||||
| 			pos_inv.payment_schedule = [] | ||||
| 	else: | ||||
| 		pos_inv.payment_schedule = [] | ||||
| 
 | ||||
| 	return pos_inv | ||||
							
								
								
									
										805
									
								
								erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										805
									
								
								erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,805 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "hash", | ||||
|  "creation": "2020-01-27 13:04:55.229516", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Document", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "barcode", | ||||
|   "item_code", | ||||
|   "col_break1", | ||||
|   "item_name", | ||||
|   "customer_item_code", | ||||
|   "description_section", | ||||
|   "description", | ||||
|   "item_group", | ||||
|   "brand", | ||||
|   "image_section", | ||||
|   "image", | ||||
|   "image_view", | ||||
|   "quantity_and_rate", | ||||
|   "qty", | ||||
|   "stock_uom", | ||||
|   "col_break2", | ||||
|   "uom", | ||||
|   "conversion_factor", | ||||
|   "stock_qty", | ||||
|   "section_break_17", | ||||
|   "price_list_rate", | ||||
|   "base_price_list_rate", | ||||
|   "discount_and_margin", | ||||
|   "margin_type", | ||||
|   "margin_rate_or_amount", | ||||
|   "rate_with_margin", | ||||
|   "column_break_19", | ||||
|   "discount_percentage", | ||||
|   "discount_amount", | ||||
|   "base_rate_with_margin", | ||||
|   "section_break1", | ||||
|   "rate", | ||||
|   "amount", | ||||
|   "item_tax_template", | ||||
|   "col_break3", | ||||
|   "base_rate", | ||||
|   "base_amount", | ||||
|   "pricing_rules", | ||||
|   "is_free_item", | ||||
|   "section_break_21", | ||||
|   "net_rate", | ||||
|   "net_amount", | ||||
|   "column_break_24", | ||||
|   "base_net_rate", | ||||
|   "base_net_amount", | ||||
|   "drop_ship", | ||||
|   "delivered_by_supplier", | ||||
|   "accounting", | ||||
|   "income_account", | ||||
|   "is_fixed_asset", | ||||
|   "asset", | ||||
|   "finance_book", | ||||
|   "col_break4", | ||||
|   "expense_account", | ||||
|   "deferred_revenue", | ||||
|   "deferred_revenue_account", | ||||
|   "service_stop_date", | ||||
|   "enable_deferred_revenue", | ||||
|   "column_break_50", | ||||
|   "service_start_date", | ||||
|   "service_end_date", | ||||
|   "section_break_18", | ||||
|   "weight_per_unit", | ||||
|   "total_weight", | ||||
|   "column_break_21", | ||||
|   "weight_uom", | ||||
|   "warehouse_and_reference", | ||||
|   "warehouse", | ||||
|   "target_warehouse", | ||||
|   "quality_inspection", | ||||
|   "batch_no", | ||||
|   "col_break5", | ||||
|   "allow_zero_valuation_rate", | ||||
|   "serial_no", | ||||
|   "item_tax_rate", | ||||
|   "actual_batch_qty", | ||||
|   "actual_qty", | ||||
|   "edit_references", | ||||
|   "sales_order", | ||||
|   "so_detail", | ||||
|   "column_break_74", | ||||
|   "delivery_note", | ||||
|   "dn_detail", | ||||
|   "delivered_qty", | ||||
|   "accounting_dimensions_section", | ||||
|   "cost_center", | ||||
|   "dimension_col_break", | ||||
|   "project", | ||||
|   "section_break_54", | ||||
|   "page_break" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "barcode", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Barcode", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "bold": 1, | ||||
|    "columns": 4, | ||||
|    "fieldname": "item_code", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Item", | ||||
|    "oldfieldname": "item_code", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Item", | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "col_break1", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "item_name", | ||||
|    "fieldtype": "Data", | ||||
|    "in_global_search": 1, | ||||
|    "label": "Item Name", | ||||
|    "oldfieldname": "item_name", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "customer_item_code", | ||||
|    "fieldtype": "Data", | ||||
|    "hidden": 1, | ||||
|    "label": "Customer's Item Code", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "description_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Description" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Text Editor", | ||||
|    "label": "Description", | ||||
|    "oldfieldname": "description", | ||||
|    "oldfieldtype": "Text", | ||||
|    "print_width": "200px", | ||||
|    "reqd": 1, | ||||
|    "width": "200px" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "item_group", | ||||
|    "fieldtype": "Link", | ||||
|    "hidden": 1, | ||||
|    "label": "Item Group", | ||||
|    "oldfieldname": "item_group", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Item Group", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "brand", | ||||
|    "fieldtype": "Data", | ||||
|    "hidden": 1, | ||||
|    "label": "Brand Name", | ||||
|    "oldfieldname": "brand", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "image_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Image" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "image", | ||||
|    "fieldtype": "Attach", | ||||
|    "hidden": 1, | ||||
|    "label": "Image" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "image_view", | ||||
|    "fieldtype": "Image", | ||||
|    "label": "Image View", | ||||
|    "options": "image", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "quantity_and_rate", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "bold": 1, | ||||
|    "columns": 2, | ||||
|    "fieldname": "qty", | ||||
|    "fieldtype": "Float", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Quantity", | ||||
|    "oldfieldname": "qty", | ||||
|    "oldfieldtype": "Currency" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "stock_uom", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Stock UOM", | ||||
|    "options": "UOM", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "col_break2", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "uom", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "UOM", | ||||
|    "options": "UOM", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "conversion_factor", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "UOM Conversion Factor", | ||||
|    "print_hide": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "stock_qty", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Qty as per Stock UOM", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_17", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "price_list_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Price List Rate", | ||||
|    "oldfieldname": "ref_rate", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "options": "currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_price_list_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Price List Rate (Company Currency)", | ||||
|    "oldfieldname": "base_ref_rate", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "discount_and_margin", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Discount and Margin" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "price_list_rate", | ||||
|    "fieldname": "margin_type", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Margin Type", | ||||
|    "options": "\nPercentage\nAmount", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.margin_type && doc.price_list_rate", | ||||
|    "fieldname": "margin_rate_or_amount", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Margin Rate or Amount", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", | ||||
|    "fieldname": "rate_with_margin", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Rate With Margin", | ||||
|    "options": "currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_19", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "price_list_rate", | ||||
|    "fieldname": "discount_percentage", | ||||
|    "fieldtype": "Percent", | ||||
|    "label": "Discount (%) on Price List Rate with Margin", | ||||
|    "oldfieldname": "adj_rate", | ||||
|    "oldfieldtype": "Float", | ||||
|    "precision": "2", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "price_list_rate", | ||||
|    "fieldname": "discount_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Discount Amount", | ||||
|    "options": "currency" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount", | ||||
|    "fieldname": "base_rate_with_margin", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Rate With Margin (Company Currency)", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break1", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "bold": 1, | ||||
|    "columns": 2, | ||||
|    "fieldname": "rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Rate", | ||||
|    "oldfieldname": "export_rate", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "options": "currency", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "columns": 2, | ||||
|    "fieldname": "amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Amount", | ||||
|    "oldfieldname": "export_amount", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "options": "currency", | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "item_tax_template", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Item Tax Template", | ||||
|    "options": "Item Tax Template", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "col_break3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Rate (Company Currency)", | ||||
|    "oldfieldname": "basic_rate", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Amount (Company Currency)", | ||||
|    "oldfieldname": "amount", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "pricing_rules", | ||||
|    "fieldtype": "Small Text", | ||||
|    "hidden": 1, | ||||
|    "label": "Pricing Rules", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "is_free_item", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Free Item", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_21", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "net_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Net Rate", | ||||
|    "options": "currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "net_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Net Amount", | ||||
|    "options": "currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_24", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_net_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Net Rate (Company Currency)", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_net_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Net Amount (Company Currency)", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "collapsible_depends_on": "eval:doc.delivered_by_supplier==1", | ||||
|    "fieldname": "drop_ship", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Drop Ship" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "delivered_by_supplier", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Delivered By Supplier", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "accounting", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Accounting Details" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "income_account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Income Account", | ||||
|    "oldfieldname": "income_account", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Account", | ||||
|    "print_hide": 1, | ||||
|    "print_width": "120px", | ||||
|    "reqd": 1, | ||||
|    "width": "120px" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "is_fixed_asset", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 1, | ||||
|    "label": "Is Fixed Asset", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "asset", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Asset", | ||||
|    "no_copy": 1, | ||||
|    "options": "Asset" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "asset", | ||||
|    "fieldname": "finance_book", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Finance Book", | ||||
|    "options": "Finance Book" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "col_break4", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "expense_account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Expense Account", | ||||
|    "options": "Account", | ||||
|    "print_hide": 1, | ||||
|    "width": "120px" | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "deferred_revenue", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Deferred Revenue" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "deferred_revenue_account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Deferred Revenue Account", | ||||
|    "options": "Account" | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "service_stop_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service Stop Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "enable_deferred_revenue", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Enable Deferred Revenue" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_50", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "service_start_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service Start Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "service_end_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service End Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "section_break_18", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Item Weight Details" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "weight_per_unit", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Weight Per Unit", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "total_weight", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Total Weight", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_21", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "weight_uom", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Weight UOM", | ||||
|    "options": "UOM", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "collapsible_depends_on": "eval:doc.serial_no || doc.batch_no", | ||||
|    "fieldname": "warehouse_and_reference", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Stock Details" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Warehouse", | ||||
|    "oldfieldname": "warehouse", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Warehouse", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "target_warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "hidden": 1, | ||||
|    "ignore_user_permissions": 1, | ||||
|    "label": "Customer Warehouse (Optional)", | ||||
|    "no_copy": 1, | ||||
|    "options": "Warehouse", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:!doc.__islocal", | ||||
|    "fieldname": "quality_inspection", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Quality Inspection", | ||||
|    "options": "Quality Inspection" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "batch_no", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Batch No", | ||||
|    "options": "Batch", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "col_break5", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_zero_valuation_rate", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Zero Valuation Rate", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "serial_no", | ||||
|    "fieldtype": "Small Text", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Serial No", | ||||
|    "oldfieldname": "serial_no", | ||||
|    "oldfieldtype": "Small Text" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "item_tax_rate", | ||||
|    "fieldtype": "Small Text", | ||||
|    "hidden": 1, | ||||
|    "label": "Item Tax Rate", | ||||
|    "oldfieldname": "item_tax_rate", | ||||
|    "oldfieldtype": "Small Text", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "actual_batch_qty", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Available Batch Qty at Warehouse", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "print_width": "150px", | ||||
|    "read_only": 1, | ||||
|    "width": "150px" | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "actual_qty", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Available Qty at Warehouse", | ||||
|    "oldfieldname": "actual_qty", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "edit_references", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "References" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "sales_order", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Sales Order", | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "sales_order", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Sales Order", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1, | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "so_detail", | ||||
|    "fieldtype": "Data", | ||||
|    "hidden": 1, | ||||
|    "label": "Sales Order Item", | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "so_detail", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1, | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_74", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "delivery_note", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Delivery Note", | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "delivery_note", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Delivery Note", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1, | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "dn_detail", | ||||
|    "fieldtype": "Data", | ||||
|    "hidden": 1, | ||||
|    "label": "Delivery Note Item", | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "dn_detail", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1, | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "delivered_qty", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Delivered Qty", | ||||
|    "oldfieldname": "delivered_qty", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "accounting_dimensions_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Accounting Dimensions" | ||||
|   }, | ||||
|   { | ||||
|    "default": ":Company", | ||||
|    "fieldname": "cost_center", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Cost Center", | ||||
|    "oldfieldname": "cost_center", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Cost Center", | ||||
|    "print_hide": 1, | ||||
|    "print_width": "120px", | ||||
|    "reqd": 1, | ||||
|    "width": "120px" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "dimension_col_break", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_54", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "default": "0", | ||||
|    "fieldname": "page_break", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Page Break", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "report_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "project", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Project", | ||||
|    "options": "Project" | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-22 13:40:34.418346", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Invoice Item", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -1,9 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # Copyright (c) 2020, 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 POSClosingVoucherInvoices(Document): | ||||
| class POSInvoiceItem(Document): | ||||
| 	pass | ||||
| @ -0,0 +1,16 @@ | ||||
| // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('POS Invoice Merge Log', { | ||||
| 	setup: function(frm) { | ||||
| 		frm.set_query("pos_invoice", "pos_invoices", doc => { | ||||
| 			return{ | ||||
| 				filters: {  | ||||
| 					'docstatus': 1, | ||||
| 					'customer': doc.customer,  | ||||
| 					'consolidated_invoice': ''  | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| @ -0,0 +1,147 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-01-28 11:56:33.945372", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "posting_date", | ||||
|   "customer", | ||||
|   "section_break_3", | ||||
|   "pos_invoices", | ||||
|   "references_section", | ||||
|   "consolidated_invoice", | ||||
|   "column_break_7", | ||||
|   "consolidated_credit_note", | ||||
|   "amended_from" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "posting_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Posting Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "customer", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Customer", | ||||
|    "options": "Customer", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_3", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "pos_invoices", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "POS Invoices", | ||||
|    "options": "POS Invoice Reference", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "references_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "References" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "amended_from", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Amended From", | ||||
|    "no_copy": 1, | ||||
|    "options": "POS Invoice Merge Log", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "consolidated_invoice", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Consolidated Sales Invoice", | ||||
|    "options": "Sales Invoice", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_7", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "consolidated_credit_note", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Consolidated Credit Note", | ||||
|    "options": "Sales Invoice", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:08:41.317100", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Invoice Merge Log", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Sales Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Sales User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Administrator", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,180 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate | ||||
| from frappe.model.document import Document | ||||
| from frappe.model.mapper import map_doc | ||||
| from frappe.model import default_fields | ||||
| 
 | ||||
| from six import iteritems | ||||
| 
 | ||||
| class POSInvoiceMergeLog(Document): | ||||
| 	def validate(self): | ||||
| 		self.validate_customer() | ||||
| 		self.validate_pos_invoice_status() | ||||
| 
 | ||||
| 	def validate_customer(self): | ||||
| 		for d in self.pos_invoices: | ||||
| 			if d.customer != self.customer: | ||||
| 				frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer)) | ||||
| 
 | ||||
| 	def validate_pos_invoice_status(self): | ||||
| 		for d in self.pos_invoices: | ||||
| 			status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus']) | ||||
| 			if docstatus != 1: | ||||
| 				frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice)) | ||||
| 			if status in ['Consolidated']: | ||||
| 				frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status)) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices] | ||||
| 
 | ||||
| 		returns = [d for d in pos_invoice_docs if d.get('is_return') == 1] | ||||
| 		sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] | ||||
| 
 | ||||
| 		sales_invoice = self.process_merging_into_sales_invoice(sales) | ||||
| 		 | ||||
| 		if len(returns): | ||||
| 			credit_note = self.process_merging_into_credit_note(returns) | ||||
| 		else: | ||||
| 			credit_note = "" | ||||
| 
 | ||||
| 		self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log | ||||
| 
 | ||||
| 		self.update_pos_invoices(sales_invoice, credit_note) | ||||
| 
 | ||||
| 	def process_merging_into_sales_invoice(self, data): | ||||
| 		sales_invoice = self.get_new_sales_invoice() | ||||
| 		 | ||||
| 		sales_invoice = self.merge_pos_invoice_into(sales_invoice, data) | ||||
| 
 | ||||
| 		sales_invoice.is_consolidated = 1 | ||||
| 		sales_invoice.save() | ||||
| 		sales_invoice.submit() | ||||
| 		self.consolidated_invoice = sales_invoice.name | ||||
| 
 | ||||
| 		return sales_invoice.name | ||||
| 
 | ||||
| 	def process_merging_into_credit_note(self, data): | ||||
| 		credit_note = self.get_new_sales_invoice() | ||||
| 		credit_note.is_return = 1 | ||||
| 
 | ||||
| 		credit_note = self.merge_pos_invoice_into(credit_note, data) | ||||
| 
 | ||||
| 		credit_note.is_consolidated = 1 | ||||
| 		# TODO: return could be against multiple sales invoice which could also have been consolidated? | ||||
| 		credit_note.return_against = self.consolidated_invoice | ||||
| 		credit_note.save() | ||||
| 		credit_note.submit() | ||||
| 		self.consolidated_credit_note = credit_note.name | ||||
| 
 | ||||
| 		return credit_note.name | ||||
| 	 | ||||
| 	def merge_pos_invoice_into(self, invoice, data): | ||||
| 		items, payments, taxes = [], [], [] | ||||
| 		loyalty_amount_sum, loyalty_points_sum = 0, 0 | ||||
| 		for doc in data: | ||||
| 			map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) | ||||
| 			 | ||||
| 			if doc.redeem_loyalty_points: | ||||
| 				invoice.loyalty_redemption_account = doc.loyalty_redemption_account | ||||
| 				invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center | ||||
| 				loyalty_points_sum += doc.loyalty_points | ||||
| 				loyalty_amount_sum += doc.loyalty_amount | ||||
| 			 | ||||
| 			for item in doc.get('items'): | ||||
| 				items.append(item) | ||||
| 			 | ||||
| 			for tax in doc.get('taxes'): | ||||
| 				found = False | ||||
| 				for t in taxes: | ||||
| 					if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate: | ||||
| 						t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount) | ||||
| 						t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount) | ||||
| 						found = True | ||||
| 				if not found: | ||||
| 					tax.charge_type = 'Actual' | ||||
| 					taxes.append(tax) | ||||
| 
 | ||||
| 			for payment in doc.get('payments'): | ||||
| 				found = False | ||||
| 				for pay in payments: | ||||
| 					if pay.account == payment.account and pay.mode_of_payment == payment.mode_of_payment: | ||||
| 						pay.amount = flt(pay.amount) + flt(payment.amount) | ||||
| 						pay.base_amount = flt(pay.base_amount) + flt(payment.base_amount) | ||||
| 						found = True | ||||
| 				if not found: | ||||
| 					payments.append(payment) | ||||
| 
 | ||||
| 		if loyalty_points_sum: | ||||
| 			invoice.redeem_loyalty_points = 1 | ||||
| 			invoice.loyalty_points = loyalty_points_sum | ||||
| 			invoice.loyalty_amount = loyalty_amount_sum | ||||
| 
 | ||||
| 		invoice.set('items', items) | ||||
| 		invoice.set('payments', payments) | ||||
| 		invoice.set('taxes', taxes) | ||||
| 
 | ||||
| 		return invoice | ||||
| 	 | ||||
| 	def get_new_sales_invoice(self): | ||||
| 		sales_invoice = frappe.new_doc('Sales Invoice') | ||||
| 		sales_invoice.customer = self.customer | ||||
| 		sales_invoice.is_pos = 1 | ||||
| 		# date can be pos closing date? | ||||
| 		sales_invoice.posting_date = getdate(nowdate()) | ||||
| 
 | ||||
| 		return sales_invoice | ||||
| 	 | ||||
| 	def update_pos_invoices(self, sales_invoice, credit_note): | ||||
| 		for d in self.pos_invoices: | ||||
| 			doc = frappe.get_doc('POS Invoice', d.pos_invoice) | ||||
| 			if not doc.is_return: | ||||
| 				doc.update({'consolidated_invoice': sales_invoice}) | ||||
| 			else: | ||||
| 				doc.update({'consolidated_invoice': credit_note}) | ||||
| 			doc.set_status(update=True) | ||||
| 			doc.save() | ||||
| 
 | ||||
| def get_all_invoices(): | ||||
| 	filters = { | ||||
| 		'consolidated_invoice': [ 'in', [ '', None ]], | ||||
| 		'status': ['not in', ['Consolidated']], | ||||
| 		'docstatus': 1 | ||||
| 	} | ||||
| 	pos_invoices = frappe.db.get_all('POS Invoice', filters=filters, | ||||
| 		fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer']) | ||||
| 	 | ||||
| 	return pos_invoices | ||||
| 
 | ||||
| def get_invoices_customer_map(pos_invoices): | ||||
| 	# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] } | ||||
| 	pos_invoice_customer_map = {} | ||||
| 	for invoice in pos_invoices: | ||||
| 		customer = invoice.get('customer') | ||||
| 		pos_invoice_customer_map.setdefault(customer, []) | ||||
| 		pos_invoice_customer_map[customer].append(invoice) | ||||
| 	 | ||||
| 	return pos_invoice_customer_map | ||||
| 
 | ||||
| def merge_pos_invoices(pos_invoices=[]): | ||||
| 	if not pos_invoices: | ||||
| 		pos_invoices = get_all_invoices() | ||||
| 	 | ||||
| 	pos_invoice_map = get_invoices_customer_map(pos_invoices) | ||||
| 	create_merge_logs(pos_invoice_map) | ||||
| 
 | ||||
| def create_merge_logs(pos_invoice_customer_map): | ||||
| 	for customer, invoices in iteritems(pos_invoice_customer_map): | ||||
| 		merge_log = frappe.new_doc('POS Invoice Merge Log') | ||||
| 		merge_log.posting_date = getdate(nowdate()) | ||||
| 		merge_log.customer = customer | ||||
| 
 | ||||
| 		merge_log.set('pos_invoices', invoices) | ||||
| 		merge_log.save(ignore_permissions=True) | ||||
| 		merge_log.submit() | ||||
| 
 | ||||
| @ -0,0 +1,98 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice | ||||
| from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return | ||||
| from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | ||||
| from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile | ||||
| 
 | ||||
| class TestPOSInvoiceMergeLog(unittest.TestCase): | ||||
| 	def test_consolidated_invoice_creation(self): | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 
 | ||||
| 		pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||
| 		pos_inv.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||
| 		}) | ||||
| 		pos_inv.submit() | ||||
| 
 | ||||
| 		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 		pos_inv2.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 		}) | ||||
| 		pos_inv2.submit() | ||||
| 
 | ||||
| 		pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) | ||||
| 		pos_inv3.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 | ||||
| 		}) | ||||
| 		pos_inv3.submit() | ||||
| 
 | ||||
| 		merge_pos_invoices() | ||||
| 
 | ||||
| 		pos_inv.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) | ||||
| 
 | ||||
| 		pos_inv3.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) | ||||
| 
 | ||||
| 		self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 	 | ||||
| 	def test_consolidated_credit_note_creation(self): | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 
 | ||||
| 		pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||
| 		pos_inv.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||
| 		}) | ||||
| 		pos_inv.submit() | ||||
| 
 | ||||
| 		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 		pos_inv2.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 		}) | ||||
| 		pos_inv2.submit() | ||||
| 
 | ||||
| 		pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) | ||||
| 		pos_inv3.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 | ||||
| 		}) | ||||
| 		pos_inv3.submit() | ||||
| 
 | ||||
| 		pos_inv_cn = make_sales_return(pos_inv.name) | ||||
| 		pos_inv_cn.set("payments", []) | ||||
| 		pos_inv_cn.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300 | ||||
| 		}) | ||||
| 		pos_inv_cn.paid_amount = -300 | ||||
| 		pos_inv_cn.submit() | ||||
| 
 | ||||
| 		merge_pos_invoices() | ||||
| 
 | ||||
| 		pos_inv.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) | ||||
| 
 | ||||
| 		pos_inv3.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) | ||||
| 
 | ||||
| 		pos_inv_cn.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) | ||||
| 		self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,65 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-01-28 11:54:47.149392", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "pos_invoice", | ||||
|   "posting_date", | ||||
|   "column_break_3", | ||||
|   "customer", | ||||
|   "grand_total" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "pos_invoice", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "POS Invoice", | ||||
|    "options": "POS Invoice", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "pos_invoice.customer", | ||||
|    "fieldname": "customer", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Customer", | ||||
|    "options": "Customer", | ||||
|    "read_only": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "pos_invoice.posting_date", | ||||
|    "fieldname": "posting_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "pos_invoice.grand_total", | ||||
|    "fieldname": "grand_total", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Amount", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:08:42.194979", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Invoice Reference", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, 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 POSInvoiceReference(Document): | ||||
| 	pass | ||||
| @ -0,0 +1,56 @@ | ||||
| // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('POS Opening Entry', { | ||||
| 	setup(frm) { | ||||
| 		if (frm.doc.docstatus == 0) { | ||||
| 			frm.trigger('set_posting_date_read_only'); | ||||
| 			frm.set_value('period_start_date', frappe.datetime.now_datetime()); | ||||
| 			frm.set_value('user', frappe.session.user); | ||||
| 		} | ||||
| 
 | ||||
| 		frm.set_query("user", function(doc) { | ||||
| 			return { | ||||
| 				query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers", | ||||
| 				filters: { 'parent': doc.pos_profile } | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	refresh(frm) { | ||||
| 		// set default posting date / time
 | ||||
| 		if(frm.doc.docstatus == 0) { | ||||
| 			if(!frm.doc.posting_date) { | ||||
| 				frm.set_value('posting_date', frappe.datetime.nowdate()); | ||||
| 			} | ||||
| 			frm.trigger('set_posting_date_read_only'); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	set_posting_date_read_only(frm) { | ||||
| 		if(frm.doc.docstatus == 0 && frm.doc.set_posting_date) { | ||||
| 			frm.set_df_property('posting_date', 'read_only', 0); | ||||
| 		} else { | ||||
| 			frm.set_df_property('posting_date', 'read_only', 1); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	set_posting_date(frm) { | ||||
| 		frm.trigger('set_posting_date_read_only'); | ||||
| 	}, | ||||
| 
 | ||||
| 	pos_profile: (frm) => { | ||||
| 		if (frm.doc.pos_profile) { | ||||
| 			frappe.db.get_doc("POS Profile", frm.doc.pos_profile) | ||||
| 				.then(({ payments }) => { | ||||
| 					if (payments.length) { | ||||
| 						frm.doc.balance_details = []; | ||||
| 						payments.forEach(({ mode_of_payment }) => { | ||||
| 							frm.add_child("balance_details", { mode_of_payment }); | ||||
| 						}) | ||||
| 						frm.refresh_field("balance_details"); | ||||
| 					} | ||||
| 				}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| @ -0,0 +1,185 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "POS-OPE-.YYYY.-.#####", | ||||
|  "creation": "2020-03-05 16:58:53.083708", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "period_start_date", | ||||
|   "period_end_date", | ||||
|   "status", | ||||
|   "column_break_3", | ||||
|   "posting_date", | ||||
|   "set_posting_date", | ||||
|   "section_break_5", | ||||
|   "company", | ||||
|   "pos_profile", | ||||
|   "pos_closing_entry", | ||||
|   "column_break_7", | ||||
|   "user", | ||||
|   "opening_balance_details_section", | ||||
|   "balance_details", | ||||
|   "section_break_9", | ||||
|   "amended_from" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "period_start_date", | ||||
|    "fieldtype": "Datetime", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Period Start Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "period_end_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Period End Date", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Today", | ||||
|    "fieldname": "posting_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Posting Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_5", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Company", | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "pos_profile", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "POS Profile", | ||||
|    "options": "POS Profile", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_7", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "user", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Cashier", | ||||
|    "options": "User", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_9", | ||||
|    "fieldtype": "Section Break", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "amended_from", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Amended From", | ||||
|    "no_copy": 1, | ||||
|    "options": "POS Opening Entry", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "set_posting_date", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Set Posting Date" | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "default": "Draft", | ||||
|    "fieldname": "status", | ||||
|    "fieldtype": "Select", | ||||
|    "hidden": 1, | ||||
|    "label": "Status", | ||||
|    "options": "Draft\nOpen\nClosed\nCancelled", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "pos_closing_entry", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "POS Closing Entry", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "opening_balance_details_section", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "balance_details", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Opening Balance Details", | ||||
|    "options": "POS Opening Entry Detail", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:08:40.955310", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Opening Entry", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Sales Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Administrator", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import cint | ||||
| from frappe.model.document import Document | ||||
| from erpnext.controllers.status_updater import StatusUpdater | ||||
| 
 | ||||
| class POSOpeningEntry(StatusUpdater): | ||||
| 	def validate(self): | ||||
| 		self.validate_pos_profile_and_cashier() | ||||
| 		self.set_status() | ||||
| 
 | ||||
| 	def validate_pos_profile_and_cashier(self): | ||||
| 		if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"): | ||||
| 			frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company))) | ||||
| 
 | ||||
| 		if not cint(frappe.db.get_value("User", self.user, "enabled")): | ||||
| 			frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user))) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		self.set_status(update=True) | ||||
| @ -0,0 +1,16 @@ | ||||
| // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 | ||||
| // License: GNU General Public License v3. See license.txt
 | ||||
| 
 | ||||
| // render
 | ||||
| frappe.listview_settings['POS Opening Entry'] = { | ||||
| 	get_indicator: function(doc) { | ||||
| 		var status_color = { | ||||
| 			"Draft": "grey", | ||||
| 			"Open": "orange", | ||||
| 			"Closed": "green", | ||||
| 			"Cancelled": "red" | ||||
| 
 | ||||
| 		}; | ||||
| 		return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; | ||||
| 	} | ||||
| }; | ||||
| @ -0,0 +1,28 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestPOSOpeningEntry(unittest.TestCase): | ||||
| 	pass | ||||
| 
 | ||||
| def create_opening_entry(pos_profile, user): | ||||
| 	entry = frappe.new_doc("POS Opening Entry") | ||||
| 	entry.pos_profile = pos_profile.name | ||||
| 	entry.user = user | ||||
| 	entry.company = pos_profile.company | ||||
| 	entry.period_start_date = frappe.utils.get_datetime() | ||||
| 
 | ||||
| 	balance_details = []; | ||||
| 	for d in pos_profile.payments: | ||||
| 		balance_details.append(frappe._dict({ | ||||
| 			'mode_of_payment': d.mode_of_payment | ||||
| 		})) | ||||
| 	 | ||||
| 	entry.set("balance_details", balance_details) | ||||
| 	entry.submit() | ||||
| 	 | ||||
| 	return entry.as_dict()	 | ||||
| @ -0,0 +1,42 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-04-28 16:44:32.440794", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "mode_of_payment", | ||||
|   "opening_amount" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "mode_of_payment", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Mode of Payment", | ||||
|    "options": "Mode of Payment", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "opening_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Opening Amount", | ||||
|    "options": "company:company_currency", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:08:41.949378", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Opening Entry Detail", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, 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 POSOpeningEntryDetail(Document): | ||||
| 	pass | ||||
| @ -0,0 +1,40 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-04-30 14:37:08.148707", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "default", | ||||
|   "mode_of_payment" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:parent.doctype == 'POS Profile'", | ||||
|    "fieldname": "default", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Default" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "mode_of_payment", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Mode of Payment", | ||||
|    "options": "Mode of Payment", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-29 15:08:41.704844", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Payment Method", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, 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 POSPaymentMethod(Document): | ||||
| 	pass | ||||
| @ -28,7 +28,7 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { | ||||
| 
 | ||||
| frappe.ui.form.on('POS Profile', { | ||||
| 	setup: function(frm) { | ||||
| 		frm.set_query("print_format_for_online", function() { | ||||
| 		frm.set_query("print_format", function() { | ||||
| 			return { | ||||
| 				filters: [ | ||||
| 					['Print Format', 'doc_type', '=', 'Sales Invoice'], | ||||
| @ -49,12 +49,6 @@ frappe.ui.form.on('POS Profile', { | ||||
| 			return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} }; | ||||
| 		}); | ||||
| 
 | ||||
| 		frappe.db.get_value('POS Settings', 'POS Settings', 'use_pos_in_offline_mode', (r) => { | ||||
| 			const is_offline = r && cint(r.use_pos_in_offline_mode) | ||||
| 			frm.toggle_display('offline_pos_section', is_offline); | ||||
| 			frm.toggle_display('print_format_for_online', !is_offline); | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query('company_address', function(doc) { | ||||
| 			if(!doc.company) { | ||||
| 				frappe.throw(__('Please set Company')); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "Prompt", | ||||
|  "creation": "2013-05-24 12:15:51", | ||||
| @ -11,17 +12,12 @@ | ||||
|   "customer", | ||||
|   "company", | ||||
|   "country", | ||||
|   "warehouse", | ||||
|   "campaign", | ||||
|   "company_address", | ||||
|   "column_break_9", | ||||
|   "update_stock", | ||||
|   "ignore_pricing_rule", | ||||
|   "allow_delete", | ||||
|   "allow_user_to_edit_rate", | ||||
|   "allow_user_to_edit_discount", | ||||
|   "allow_print_before_pay", | ||||
|   "display_items_in_stock", | ||||
|   "warehouse", | ||||
|   "campaign", | ||||
|   "company_address", | ||||
|   "section_break_15", | ||||
|   "applicable_for_users", | ||||
|   "section_break_11", | ||||
| @ -31,16 +27,11 @@ | ||||
|   "column_break_16", | ||||
|   "customer_groups", | ||||
|   "section_break_16", | ||||
|   "print_format_for_online", | ||||
|   "print_format", | ||||
|   "letter_head", | ||||
|   "column_break0", | ||||
|   "tc_name", | ||||
|   "select_print_heading", | ||||
|   "offline_pos_section", | ||||
|   "territory", | ||||
|   "column_break_31", | ||||
|   "print_format", | ||||
|   "customer_group", | ||||
|   "section_break_19", | ||||
|   "selling_price_list", | ||||
|   "currency", | ||||
| @ -104,15 +95,6 @@ | ||||
|    "fieldtype": "Read Only", | ||||
|    "label": "Country" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "update_stock", | ||||
|    "fieldname": "warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Warehouse", | ||||
|    "oldfieldname": "warehouse", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Warehouse" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "campaign", | ||||
|    "fieldtype": "Link", | ||||
| @ -129,48 +111,6 @@ | ||||
|    "fieldname": "column_break_9", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "update_stock", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Update Stock" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "ignore_pricing_rule", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Ignore Pricing Rule" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_delete", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Delete" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_user_to_edit_rate", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow user to edit Rate" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_user_to_edit_discount", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow user to edit Discount" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_print_before_pay", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Print Before Pay" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "display_items_in_stock", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Display Items In Stock" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_15", | ||||
|    "fieldtype": "Section Break", | ||||
| @ -185,13 +125,13 @@ | ||||
|   { | ||||
|    "fieldname": "section_break_11", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Mode of Payment" | ||||
|    "label": "Payment Methods" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "payments", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Sales Invoice Payment", | ||||
|    "options": "Sales Invoice Payment" | ||||
|    "options": "POS Payment Method", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_14", | ||||
| @ -220,12 +160,6 @@ | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Print Settings" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "print_format_for_online", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Print Format for Online", | ||||
|    "options": "Print Format" | ||||
|   }, | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "letter_head", | ||||
| @ -258,39 +192,6 @@ | ||||
|    "oldfieldtype": "Select", | ||||
|    "options": "Print Heading" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "offline_pos_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Offline POS Settings" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "territory", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Territory", | ||||
|    "oldfieldname": "territory", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Territory", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_31", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Point of Sale", | ||||
|    "fieldname": "print_format", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Print Format", | ||||
|    "options": "Print Format" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "customer_group", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Customer Group", | ||||
|    "options": "Customer Group", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_19", | ||||
|    "fieldtype": "Section Break", | ||||
| @ -380,20 +281,49 @@ | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Accounting Dimensions" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "dimension_col_break", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "tax_category", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Tax Category", | ||||
|    "options": "Tax Category" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "dimension_col_break", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "print_format", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Print Format", | ||||
|    "options": "Print Format" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "update_stock", | ||||
|    "fieldname": "warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Warehouse", | ||||
|    "oldfieldname": "warehouse", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Warehouse", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "update_stock", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Update Stock" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "ignore_pricing_rule", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Ignore Pricing Rule" | ||||
|   } | ||||
|  ], | ||||
|  "icon": "icon-cog", | ||||
|  "idx": 1, | ||||
|  "modified": "2020-01-24 15:52:03.797701", | ||||
|  "links": [], | ||||
|  "modified": "2020-06-29 12:20:30.977272", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Profile", | ||||
|  | ||||
| @ -5,8 +5,6 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import msgprint, _ | ||||
| from frappe.utils import cint, now | ||||
| from erpnext.accounts.doctype.sales_invoice.pos import get_child_nodes | ||||
| from erpnext.accounts.doctype.sales_invoice.sales_invoice import set_account_for_mode_of_payment | ||||
| from six import iteritems | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| @ -16,7 +14,6 @@ class POSProfile(Document): | ||||
| 		self.validate_all_link_fields() | ||||
| 		self.validate_duplicate_groups() | ||||
| 		self.check_default_payment() | ||||
| 		self.validate_customer_territory_group() | ||||
| 
 | ||||
| 	def validate_default_profile(self): | ||||
| 		for row in self.applicable_for_users: | ||||
| @ -64,19 +61,6 @@ class POSProfile(Document): | ||||
| 			if len(default_mode_of_payment) > 1: | ||||
| 				frappe.throw(_("Multiple default mode of payment is not allowed")) | ||||
| 
 | ||||
| 	def validate_customer_territory_group(self): | ||||
| 		if not frappe.db.get_single_value('POS Settings', 'use_pos_in_offline_mode'): | ||||
| 			return | ||||
| 
 | ||||
| 		if not self.territory: | ||||
| 			frappe.throw(_("Territory is Required in POS Profile"), title="Mandatory Field") | ||||
| 
 | ||||
| 		if not self.customer_group: | ||||
| 			frappe.throw(_("Customer Group is Required in POS Profile"), title="Mandatory Field") | ||||
| 
 | ||||
| 	def before_save(self): | ||||
| 		set_account_for_mode_of_payment(self) | ||||
| 
 | ||||
| 	def on_update(self): | ||||
| 		self.set_defaults() | ||||
| 
 | ||||
| @ -111,9 +95,14 @@ def get_item_groups(pos_profile): | ||||
| 
 | ||||
| 	return list(set(item_groups)) | ||||
| 
 | ||||
| def get_child_nodes(group_type, root): | ||||
| 	lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) | ||||
| 	return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where | ||||
| 			lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_series(): | ||||
| 	return frappe.get_meta("Sales Invoice").get_field("naming_series").options or "" | ||||
| 	return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s" | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): | ||||
|  | ||||
| @ -8,7 +8,7 @@ def get_data(): | ||||
| 		'fieldname': 'pos_profile', | ||||
| 		'transactions': [ | ||||
| 			{ | ||||
| 				'items': ['Sales Invoice', 'POS Closing Voucher'] | ||||
| 				'items': ['Sales Invoice', 'POS Closing Entry', 'POS Opening Entry'] | ||||
| 			} | ||||
| 		] | ||||
| 	} | ||||
|  | ||||
| @ -6,7 +6,7 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| import unittest | ||||
| from erpnext.stock.get_item_details import get_pos_profile | ||||
| from erpnext.accounts.doctype.sales_invoice.pos import get_items_list, get_customers_list | ||||
| from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes | ||||
| 
 | ||||
| class TestPOSProfile(unittest.TestCase): | ||||
| 	def test_pos_profile(self): | ||||
| @ -29,6 +29,44 @@ class TestPOSProfile(unittest.TestCase): | ||||
| 
 | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| def get_customers_list(pos_profile={}): | ||||
| 	cond = "1=1" | ||||
| 	customer_groups = [] | ||||
| 	if pos_profile.get('customer_groups'): | ||||
| 		# Get customers based on the customer groups defined in the POS profile | ||||
| 		for d in pos_profile.get('customer_groups'): | ||||
| 			customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))]) | ||||
| 		cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups))) | ||||
| 
 | ||||
| 	return frappe.db.sql(""" select name, customer_name, customer_group, | ||||
| 		territory, customer_pos_id from tabCustomer where disabled = 0 | ||||
| 		and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} | ||||
| 
 | ||||
| def get_items_list(pos_profile, company): | ||||
| 	cond = "" | ||||
| 	args_list = [] | ||||
| 	if pos_profile.get('item_groups'): | ||||
| 		# Get items based on the item groups defined in the POS profile | ||||
| 		for d in pos_profile.get('item_groups'): | ||||
| 			args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)]) | ||||
| 		if args_list: | ||||
| 			cond = "and i.item_group in (%s)" % (', '.join(['%s'] * len(args_list))) | ||||
| 
 | ||||
| 	return frappe.db.sql(""" | ||||
| 		select | ||||
| 			i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no, | ||||
| 			i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image, | ||||
| 			id.expense_account, id.selling_cost_center, id.default_warehouse, | ||||
| 			i.sales_uom, c.conversion_factor | ||||
| 		from | ||||
| 			`tabItem` i | ||||
| 		left join `tabItem Default` id on id.parent = i.name and id.company = %s | ||||
| 		left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom | ||||
| 		where | ||||
| 			i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0 | ||||
| 			{cond} | ||||
| 		""".format(cond=cond), tuple([company] + args_list), as_dict=1) | ||||
| 
 | ||||
| def make_pos_profile(**args): | ||||
| 	frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| @ -50,6 +88,12 @@ def make_pos_profile(**args): | ||||
| 		"write_off_account":  args.write_off_account or "_Test Write Off - _TC", | ||||
| 		"write_off_cost_center":  args.write_off_cost_center or "_Test Write Off Cost Center - _TC" | ||||
| 	}) | ||||
| 	 | ||||
| 	payments = [{ | ||||
| 		'mode_of_payment': 'Cash', | ||||
| 		'default': 1 | ||||
| 	}] | ||||
| 	pos_profile.set("payments", payments) | ||||
| 
 | ||||
| 	if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"): | ||||
| 		pos_profile.insert() | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-01 09:46:47.599173", | ||||
|  "modified": "2020-05-13 23:57:33.627305", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Profile User", | ||||
|  | ||||
| @ -6,27 +6,19 @@ frappe.ui.form.on('POS Settings', { | ||||
| 		frm.trigger("get_invoice_fields"); | ||||
| 	}, | ||||
| 
 | ||||
| 	use_pos_in_offline_mode: function(frm) { | ||||
| 		frm.trigger("get_invoice_fields"); | ||||
| 	}, | ||||
| 
 | ||||
| 	get_invoice_fields: function(frm) { | ||||
| 		if (!frm.doc.use_pos_in_offline_mode) { | ||||
| 			frappe.model.with_doctype("Sales Invoice", () => { | ||||
| 				var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) { | ||||
| 					if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || | ||||
| 						d.fieldtype === 'Table') { | ||||
| 						return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; | ||||
| 					} else { | ||||
| 						return null; | ||||
| 					} | ||||
| 				}); | ||||
| 
 | ||||
| 				frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields); | ||||
| 		frappe.model.with_doctype("Sales Invoice", () => { | ||||
| 			var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) { | ||||
| 				if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || | ||||
| 					d.fieldtype === 'Table') { | ||||
| 					return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; | ||||
| 				} else { | ||||
| 					return null; | ||||
| 				} | ||||
| 			}); | ||||
| 		} else { | ||||
| 			frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""]; | ||||
| 		} | ||||
| 
 | ||||
| 			frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields); | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -5,24 +5,11 @@ | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "use_pos_in_offline_mode", | ||||
|   "section_break_2", | ||||
|   "fields" | ||||
|   "invoice_fields" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "use_pos_in_offline_mode", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Use POS in Offline Mode" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_2", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:!doc.use_pos_in_offline_mode", | ||||
|    "fieldname": "fields", | ||||
|    "fieldname": "invoice_fields", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "POS Field", | ||||
|    "options": "POS Field" | ||||
| @ -30,7 +17,7 @@ | ||||
|  ], | ||||
|  "issingle": 1, | ||||
|  "links": [], | ||||
|  "modified": "2019-12-26 11:50:47.122997", | ||||
|  "modified": "2020-06-01 15:46:41.478928", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Settings", | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,626 +0,0 @@ | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # License: GNU General Public License v3. See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import json | ||||
| 
 | ||||
| import frappe | ||||
| from erpnext.accounts.party import get_party_account_currency | ||||
| from erpnext.controllers.accounts_controller import get_taxes_and_charges | ||||
| from erpnext.setup.utils import get_exchange_rate | ||||
| from erpnext.stock.get_item_details import get_pos_profile | ||||
| from frappe import _ | ||||
| from frappe.core.doctype.communication.email import make | ||||
| from frappe.utils import nowdate, cint | ||||
| 
 | ||||
| from six import string_types, iteritems | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_pos_data(): | ||||
| 	doc = frappe.new_doc('Sales Invoice') | ||||
| 	doc.is_pos = 1 | ||||
| 	pos_profile = get_pos_profile(doc.company) or {} | ||||
| 	if not pos_profile: | ||||
| 		frappe.throw(_("POS Profile is required to use Point-of-Sale")) | ||||
| 
 | ||||
| 	if not doc.company: | ||||
| 		doc.company = pos_profile.get('company') | ||||
| 
 | ||||
| 	doc.update_stock = pos_profile.get('update_stock') | ||||
| 
 | ||||
| 	if pos_profile.get('name'): | ||||
| 		pos_profile = frappe.get_doc('POS Profile', pos_profile.get('name')) | ||||
| 		pos_profile.validate() | ||||
| 
 | ||||
| 	company_data = get_company_data(doc.company) | ||||
| 	update_pos_profile_data(doc, pos_profile, company_data) | ||||
| 	update_multi_mode_option(doc, pos_profile) | ||||
| 	default_print_format = pos_profile.get('print_format') or "Point of Sale" | ||||
| 	print_template = frappe.db.get_value('Print Format', default_print_format, 'html') | ||||
| 	items_list = get_items_list(pos_profile, doc.company) | ||||
| 	customers = get_customers_list(pos_profile) | ||||
| 
 | ||||
| 	doc.plc_conversion_rate = update_plc_conversion_rate(doc, pos_profile) | ||||
| 
 | ||||
| 	return { | ||||
| 		'doc': doc, | ||||
| 		'default_customer': pos_profile.get('customer'), | ||||
| 		'items': items_list, | ||||
| 		'item_groups': get_item_groups(pos_profile), | ||||
| 		'customers': customers, | ||||
| 		'address': get_customers_address(customers), | ||||
| 		'contacts': get_contacts(customers), | ||||
| 		'serial_no_data': get_serial_no_data(pos_profile, doc.company), | ||||
| 		'batch_no_data': get_batch_no_data(), | ||||
| 		'barcode_data': get_barcode_data(items_list), | ||||
| 		'tax_data': get_item_tax_data(), | ||||
| 		'price_list_data': get_price_list_data(doc.selling_price_list, doc.plc_conversion_rate), | ||||
| 		'customer_wise_price_list': get_customer_wise_price_list(), | ||||
| 		'bin_data': get_bin_data(pos_profile), | ||||
| 		'pricing_rules': get_pricing_rule_data(doc), | ||||
| 		'print_template': print_template, | ||||
| 		'pos_profile': pos_profile, | ||||
| 		'meta': get_meta() | ||||
| 	} | ||||
| 
 | ||||
| def update_plc_conversion_rate(doc, pos_profile): | ||||
| 	conversion_rate = 1.0 | ||||
| 
 | ||||
| 	price_list_currency = frappe.get_cached_value("Price List", doc.selling_price_list, "currency") | ||||
| 	if pos_profile.get("currency") != price_list_currency: | ||||
| 		conversion_rate = get_exchange_rate(price_list_currency, | ||||
| 			pos_profile.get("currency"), nowdate(), args="for_selling") or 1.0 | ||||
| 
 | ||||
| 	return conversion_rate | ||||
| 
 | ||||
| def get_meta(): | ||||
| 	doctype_meta = { | ||||
| 		'customer': frappe.get_meta('Customer'), | ||||
| 		'invoice': frappe.get_meta('Sales Invoice') | ||||
| 	} | ||||
| 
 | ||||
| 	for row in frappe.get_all('DocField', fields=['fieldname', 'options'], | ||||
|             filters={'parent': 'Sales Invoice', 'fieldtype': 'Table'}): | ||||
| 		doctype_meta[row.fieldname] = frappe.get_meta(row.options) | ||||
| 
 | ||||
| 	return doctype_meta | ||||
| 
 | ||||
| 
 | ||||
| def get_company_data(company): | ||||
| 	return frappe.get_all('Company', fields=["*"], filters={'name': company})[0] | ||||
| 
 | ||||
| 
 | ||||
| def update_pos_profile_data(doc, pos_profile, company_data): | ||||
| 	doc.campaign = pos_profile.get('campaign') | ||||
| 	if pos_profile and not pos_profile.get('country'): | ||||
| 		pos_profile.country = company_data.country | ||||
| 
 | ||||
| 	doc.write_off_account = pos_profile.get('write_off_account') or \ | ||||
| 		company_data.write_off_account | ||||
| 	doc.change_amount_account = pos_profile.get('change_amount_account') or \ | ||||
| 		company_data.default_cash_account | ||||
| 	doc.taxes_and_charges = pos_profile.get('taxes_and_charges') | ||||
| 	if doc.taxes_and_charges: | ||||
| 		update_tax_table(doc) | ||||
| 
 | ||||
| 	doc.currency = pos_profile.get('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.posting_date, args="for_selling") | ||||
| 
 | ||||
| 	doc.selling_price_list = pos_profile.get('selling_price_list') or \ | ||||
| 		frappe.db.get_value('Selling Settings', None, 'selling_price_list') | ||||
| 	doc.naming_series = pos_profile.get('naming_series') or 'SINV-' | ||||
| 	doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head | ||||
| 	doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0 | ||||
| 	doc.apply_discount_on = pos_profile.get('apply_discount_on') or 'Grand Total' | ||||
| 	doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group') | ||||
| 	doc.territory = pos_profile.get('territory') or get_root('Territory') | ||||
| 	doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or '' | ||||
| 	doc.offline_pos_name = '' | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 	if not pos_profile or not pos_profile.get('payments'): | ||||
| 		for payment in get_mode_of_payment(doc): | ||||
| 			payments = doc.append('payments', {}) | ||||
| 			payments.mode_of_payment = payment.parent | ||||
| 			payments.account = payment.default_account | ||||
| 			payments.type = payment.type | ||||
| 
 | ||||
| 		return | ||||
| 
 | ||||
| 	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 get_mode_of_payment(doc): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type  | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp  | ||||
| 		where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", | ||||
| 	{'company': doc.company}, as_dict=1) | ||||
| 
 | ||||
| 
 | ||||
| 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_list(pos_profile, company): | ||||
| 	cond = "" | ||||
| 	args_list = [] | ||||
| 	if pos_profile.get('item_groups'): | ||||
| 		# Get items based on the item groups defined in the POS profile | ||||
| 		for d in pos_profile.get('item_groups'): | ||||
| 			args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)]) | ||||
| 		if args_list: | ||||
| 			cond = "and i.item_group in (%s)" % (', '.join(['%s'] * len(args_list))) | ||||
| 
 | ||||
| 	return frappe.db.sql(""" | ||||
| 		select | ||||
| 			i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no, | ||||
| 			i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image, | ||||
| 			id.expense_account, id.selling_cost_center, id.default_warehouse, | ||||
| 			i.sales_uom, c.conversion_factor | ||||
| 		from | ||||
| 			`tabItem` i | ||||
| 		left join `tabItem Default` id on id.parent = i.name and id.company = %s | ||||
| 		left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom | ||||
| 		where | ||||
| 			i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 | ||||
| 			{cond} | ||||
| 		""".format(cond=cond), tuple([company] + args_list), as_dict=1) | ||||
| 
 | ||||
| 
 | ||||
| def get_item_groups(pos_profile): | ||||
| 	item_group_dict = {} | ||||
| 	item_groups = frappe.db.sql("""Select name, | ||||
| 		lft, rgt from `tabItem Group` order by lft""", as_dict=1) | ||||
| 
 | ||||
| 	for data in item_groups: | ||||
| 		item_group_dict[data.name] = [data.lft, data.rgt] | ||||
| 	return item_group_dict | ||||
| 
 | ||||
| 
 | ||||
| def get_customers_list(pos_profile={}): | ||||
| 	cond = "1=1" | ||||
| 	customer_groups = [] | ||||
| 	if pos_profile.get('customer_groups'): | ||||
| 		# Get customers based on the customer groups defined in the POS profile | ||||
| 		for d in pos_profile.get('customer_groups'): | ||||
| 			customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))]) | ||||
| 		cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups))) | ||||
| 
 | ||||
| 	return frappe.db.sql(""" select name, customer_name, customer_group, | ||||
| 		territory, customer_pos_id from tabCustomer where disabled = 0 | ||||
| 		and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} | ||||
| 
 | ||||
| 
 | ||||
| def get_customers_address(customers): | ||||
| 	customer_address = {} | ||||
| 	if isinstance(customers, string_types): | ||||
| 		customers = [frappe._dict({'name': customers})] | ||||
| 
 | ||||
| 	for data in customers: | ||||
| 		address = frappe.db.sql(""" select name, address_line1, address_line2, city, state, | ||||
| 			email_id, phone, fax, pincode from `tabAddress` where is_primary_address =1 and name in | ||||
| 			(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s | ||||
| 			and parenttype = 'Address')""", data.name, as_dict=1) | ||||
| 		address_data = {} | ||||
| 		if address: | ||||
| 			address_data = address[0] | ||||
| 
 | ||||
| 		address_data.update({'full_name': data.customer_name, 'customer_pos_id': data.customer_pos_id}) | ||||
| 		customer_address[data.name] = address_data | ||||
| 
 | ||||
| 	return customer_address | ||||
| 
 | ||||
| 
 | ||||
| def get_contacts(customers): | ||||
| 	customer_contact = {} | ||||
| 	if isinstance(customers, string_types): | ||||
| 		customers = [frappe._dict({'name': customers})] | ||||
| 
 | ||||
| 	for data in customers: | ||||
| 		contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` | ||||
| 			where is_primary_contact=1 and name in | ||||
| 			(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s | ||||
| 			and parenttype = 'Contact')""", data.name, as_dict=1) | ||||
| 		if contact: | ||||
| 			customer_contact[data.name] = contact[0] | ||||
| 
 | ||||
| 	return customer_contact | ||||
| 
 | ||||
| 
 | ||||
| def get_child_nodes(group_type, root): | ||||
| 	lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) | ||||
| 	return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where | ||||
| 			lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) | ||||
| 
 | ||||
| 
 | ||||
| def get_serial_no_data(pos_profile, company): | ||||
| 	# get itemwise serial no data | ||||
| 	# example {'Nokia Lumia 1020': {'SN0001': 'Pune'}} | ||||
| 	# where Nokia Lumia 1020 is item code, SN0001 is serial no and Pune is warehouse | ||||
| 
 | ||||
| 	cond = "1=1" | ||||
| 	if pos_profile.get('update_stock') and pos_profile.get('warehouse'): | ||||
| 		cond = "warehouse = %(warehouse)s" | ||||
| 
 | ||||
| 	serial_nos = frappe.db.sql("""select name, warehouse, item_code | ||||
| 		from `tabSerial No` where {0} and company = %(company)s """.format(cond),{ | ||||
| 			'company': company, 'warehouse': frappe.db.escape(pos_profile.get('warehouse')) | ||||
| 		}, as_dict=1) | ||||
| 
 | ||||
| 	itemwise_serial_no = {} | ||||
| 	for sn in serial_nos: | ||||
| 		if sn.item_code not in itemwise_serial_no: | ||||
| 			itemwise_serial_no.setdefault(sn.item_code, {}) | ||||
| 		itemwise_serial_no[sn.item_code][sn.name] = sn.warehouse | ||||
| 
 | ||||
| 	return itemwise_serial_no | ||||
| 
 | ||||
| 
 | ||||
| def get_batch_no_data(): | ||||
| 	# get itemwise batch no data | ||||
| 	# exmaple: {'LED-GRE': [Batch001, Batch002]} | ||||
| 	# where LED-GRE is item code, SN0001 is serial no and Pune is warehouse | ||||
| 
 | ||||
| 	itemwise_batch = {} | ||||
| 	batches = frappe.db.sql("""select name, item from `tabBatch` | ||||
| 		where ifnull(expiry_date, '4000-10-10') >= curdate()""", as_dict=1) | ||||
| 
 | ||||
| 	for batch in batches: | ||||
| 		if batch.item not in itemwise_batch: | ||||
| 			itemwise_batch.setdefault(batch.item, []) | ||||
| 		itemwise_batch[batch.item].append(batch.name) | ||||
| 
 | ||||
| 	return itemwise_batch | ||||
| 
 | ||||
| 
 | ||||
| def get_barcode_data(items_list): | ||||
| 	# get itemwise batch no data | ||||
| 	# exmaple: {'LED-GRE': [Batch001, Batch002]} | ||||
| 	# where LED-GRE is item code, SN0001 is serial no and Pune is warehouse | ||||
| 
 | ||||
| 	itemwise_barcode = {} | ||||
| 	for item in items_list: | ||||
| 		barcodes = frappe.db.sql(""" | ||||
| 			select barcode from `tabItem Barcode` where parent = %s | ||||
| 		""", item.item_code, as_dict=1) | ||||
| 
 | ||||
| 		for barcode in barcodes: | ||||
| 			if item.item_code not in itemwise_barcode: | ||||
| 				itemwise_barcode.setdefault(item.item_code, []) | ||||
| 			itemwise_barcode[item.item_code].append(barcode.get("barcode")) | ||||
| 
 | ||||
| 	return itemwise_barcode | ||||
| 
 | ||||
| 
 | ||||
| def get_item_tax_data(): | ||||
| 	# get default tax of an item | ||||
| 	# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} | ||||
| 
 | ||||
| 	itemwise_tax = {} | ||||
| 	taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1) | ||||
| 
 | ||||
| 	for tax in taxes: | ||||
| 		if tax.parent not in itemwise_tax: | ||||
| 			itemwise_tax.setdefault(tax.parent, {}) | ||||
| 		itemwise_tax[tax.parent][tax.tax_type] = tax.tax_rate | ||||
| 
 | ||||
| 	return itemwise_tax | ||||
| 
 | ||||
| 
 | ||||
| def get_price_list_data(selling_price_list, conversion_rate): | ||||
| 	itemwise_price_list = {} | ||||
| 	price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate, | ||||
| 		item_code from `tabItem Price` ip where price_list = %(price_list)s""", | ||||
|         {'price_list': selling_price_list}, as_dict=1) | ||||
| 
 | ||||
| 	for item in price_lists: | ||||
| 		itemwise_price_list[item.item_code] = item.price_list_rate * conversion_rate | ||||
| 
 | ||||
| 	return itemwise_price_list | ||||
| 
 | ||||
| def get_customer_wise_price_list(): | ||||
| 	customer_wise_price = {} | ||||
| 	customer_price_list_mapping = frappe._dict(frappe.get_all('Customer',fields = ['default_price_list', 'name'], as_list=1)) | ||||
| 
 | ||||
| 	price_lists = frappe.db.sql(""" Select ifnull(price_list_rate, 0) as price_list_rate, | ||||
| 		item_code, price_list from `tabItem Price` """, as_dict=1) | ||||
| 
 | ||||
| 	for item in price_lists: | ||||
| 		if item.price_list and customer_price_list_mapping.get(item.price_list): | ||||
| 
 | ||||
| 			customer_wise_price.setdefault(customer_price_list_mapping.get(item.price_list),{}).setdefault( | ||||
| 				item.item_code, item.price_list_rate | ||||
| 			) | ||||
| 
 | ||||
| 	return customer_wise_price | ||||
| 
 | ||||
| def get_bin_data(pos_profile): | ||||
| 	itemwise_bin_data = {} | ||||
| 	filters = { 'actual_qty': ['>', 0] } | ||||
| 	if pos_profile.get('warehouse'): | ||||
| 		filters.update({ 'warehouse': pos_profile.get('warehouse') }) | ||||
| 
 | ||||
| 	bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters) | ||||
| 
 | ||||
| 	for bins in bin_data: | ||||
| 		if bins.item_code not in itemwise_bin_data: | ||||
| 			itemwise_bin_data.setdefault(bins.item_code, {}) | ||||
| 		itemwise_bin_data[bins.item_code][bins.warehouse] = bins.actual_qty | ||||
| 
 | ||||
| 	return itemwise_bin_data | ||||
| 
 | ||||
| 
 | ||||
| def get_pricing_rule_data(doc): | ||||
| 	pricing_rules = "" | ||||
| 	if doc.ignore_pricing_rule == 0: | ||||
| 		pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2 | ||||
| 						and ifnull(for_price_list, '') in (%(price_list)s, '') and selling = 1 | ||||
| 						and ifnull(company, '') in (%(company)s, '') and disable = 0 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) | ||||
| 	return pricing_rules | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={}): | ||||
| 	import json | ||||
| 
 | ||||
| 	if isinstance(doc_list, string_types): | ||||
| 		doc_list = json.loads(doc_list) | ||||
| 
 | ||||
| 	if isinstance(email_queue_list, string_types): | ||||
| 		email_queue_list = json.loads(email_queue_list) | ||||
| 
 | ||||
| 	if isinstance(customers_list, string_types): | ||||
| 		customers_list = json.loads(customers_list) | ||||
| 
 | ||||
| 	customers_list = make_customer_and_address(customers_list) | ||||
| 	name_list = [] | ||||
| 	for docs in doc_list: | ||||
| 		for name, doc in iteritems(docs): | ||||
| 			if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): | ||||
| 				if isinstance(doc, dict): | ||||
| 					validate_records(doc) | ||||
| 					si_doc = frappe.new_doc('Sales Invoice') | ||||
| 					si_doc.offline_pos_name = name | ||||
| 					si_doc.update(doc) | ||||
| 					si_doc.set_posting_time = 1 | ||||
| 					si_doc.customer = get_customer_id(doc) | ||||
| 					si_doc.due_date = doc.get('posting_date') | ||||
| 					name_list = submit_invoice(si_doc, name, doc, name_list) | ||||
| 				else: | ||||
| 					doc.due_date = doc.get('posting_date') | ||||
| 					doc.customer = get_customer_id(doc) | ||||
| 					doc.set_posting_time = 1 | ||||
| 					doc.offline_pos_name = name | ||||
| 					name_list = submit_invoice(doc, name, doc, name_list) | ||||
| 			else: | ||||
| 				name_list.append(name) | ||||
| 
 | ||||
| 	email_queue = make_email_queue(email_queue_list) | ||||
| 
 | ||||
| 	if isinstance(pos_profile, string_types): | ||||
| 		pos_profile = json.loads(pos_profile) | ||||
| 
 | ||||
| 	customers = get_customers_list(pos_profile) | ||||
| 	return { | ||||
| 		'invoice': name_list, | ||||
| 		'email_queue': email_queue, | ||||
| 		'customers': customers_list, | ||||
| 		'synced_customers_list': customers, | ||||
| 		'synced_address': get_customers_address(customers), | ||||
| 		'synced_contacts': get_contacts(customers) | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| def validate_records(doc): | ||||
| 	validate_item(doc) | ||||
| 
 | ||||
| 
 | ||||
| def get_customer_id(doc, customer=None): | ||||
| 	cust_id = None | ||||
| 	if doc.get('customer_pos_id'): | ||||
| 		cust_id = frappe.db.get_value('Customer',{'customer_pos_id': doc.get('customer_pos_id')}, 'name') | ||||
| 
 | ||||
| 	if not cust_id: | ||||
| 		customer = customer or doc.get('customer') | ||||
| 		if frappe.db.exists('Customer', customer): | ||||
| 			cust_id = customer | ||||
| 		else: | ||||
| 			cust_id = add_customer(doc) | ||||
| 
 | ||||
| 	return cust_id | ||||
| 
 | ||||
| def make_customer_and_address(customers): | ||||
| 	customers_list = [] | ||||
| 	for customer, data in iteritems(customers): | ||||
| 		data = json.loads(data) | ||||
| 		cust_id = get_customer_id(data, customer) | ||||
| 		if not cust_id: | ||||
| 			cust_id = add_customer(data) | ||||
| 		else: | ||||
| 			frappe.db.set_value("Customer", cust_id, "customer_name", data.get('full_name')) | ||||
| 
 | ||||
| 		make_contact(data, cust_id) | ||||
| 		make_address(data, cust_id) | ||||
| 		customers_list.append(customer) | ||||
| 	frappe.db.commit() | ||||
| 	return customers_list | ||||
| 
 | ||||
| def add_customer(data): | ||||
| 	customer = data.get('full_name') or data.get('customer') | ||||
| 	if frappe.db.exists("Customer", customer.strip()): | ||||
| 		return customer.strip() | ||||
| 
 | ||||
| 	customer_doc = frappe.new_doc('Customer') | ||||
| 	customer_doc.customer_name = data.get('full_name') or data.get('customer') | ||||
| 	customer_doc.customer_pos_id = data.get('customer_pos_id') | ||||
| 	customer_doc.customer_type = 'Company' | ||||
| 	customer_doc.customer_group = get_customer_group(data) | ||||
| 	customer_doc.territory = get_territory(data) | ||||
| 	customer_doc.flags.ignore_mandatory = True | ||||
| 	customer_doc.save(ignore_permissions=True) | ||||
| 	frappe.db.commit() | ||||
| 	return customer_doc.name | ||||
| 
 | ||||
| def get_territory(data): | ||||
| 	if data.get('territory'): | ||||
| 		return data.get('territory') | ||||
| 
 | ||||
| 	return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories') | ||||
| 
 | ||||
| def get_customer_group(data): | ||||
| 	if data.get('customer_group'): | ||||
| 		return data.get('customer_group') | ||||
| 
 | ||||
| 	return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name') | ||||
| 
 | ||||
| def make_contact(args, customer): | ||||
| 	if args.get('email_id') or args.get('phone'): | ||||
| 		name = frappe.db.get_value('Dynamic Link', | ||||
|             	{'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent') | ||||
| 
 | ||||
| 		args = { | ||||
| 			'first_name': args.get('full_name'), | ||||
| 			'email_id': args.get('email_id'), | ||||
| 			'phone': args.get('phone') | ||||
| 		} | ||||
| 
 | ||||
| 		doc = frappe.new_doc('Contact') | ||||
| 		if name: | ||||
| 			doc = frappe.get_doc('Contact', name) | ||||
| 
 | ||||
| 		doc.update(args) | ||||
| 		doc.is_primary_contact = 1 | ||||
| 		if not name: | ||||
| 			doc.append('links', { | ||||
| 				'link_doctype': 'Customer', | ||||
| 				'link_name': customer | ||||
| 			}) | ||||
| 		doc.flags.ignore_mandatory = True | ||||
| 		doc.save(ignore_permissions=True) | ||||
| 
 | ||||
| def make_address(args, customer): | ||||
| 	if not args.get('address_line1'): | ||||
| 		return | ||||
| 
 | ||||
| 	name = args.get('name') | ||||
| 
 | ||||
| 	if not name: | ||||
| 		data = get_customers_address(customer) | ||||
| 		name = data[customer].get('name') if data else None | ||||
| 
 | ||||
| 	if name: | ||||
| 		address = frappe.get_doc('Address', name) | ||||
| 	else: | ||||
| 		address = frappe.new_doc('Address') | ||||
| 		if args.get('company'): | ||||
| 			address.country = frappe.get_cached_value('Company', | ||||
| 				args.get('company'),  'country') | ||||
| 
 | ||||
| 		address.append('links', { | ||||
| 			'link_doctype': 'Customer', | ||||
| 			'link_name': customer | ||||
| 		}) | ||||
| 
 | ||||
| 	address.is_primary_address = 1 | ||||
| 	address.is_shipping_address = 1 | ||||
| 	address.update(args) | ||||
| 	address.flags.ignore_mandatory = True | ||||
| 	address.save(ignore_permissions=True) | ||||
| 
 | ||||
| def make_email_queue(email_queue): | ||||
| 	name_list = [] | ||||
| 
 | ||||
| 	for key, data in iteritems(email_queue): | ||||
| 		name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name') | ||||
| 		if not name: continue | ||||
| 
 | ||||
| 		data = json.loads(data) | ||||
| 		sender = frappe.session.user | ||||
| 		print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None | ||||
| 
 | ||||
| 		attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)] | ||||
| 
 | ||||
| 		make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'), | ||||
|                     sender=sender, attachments=attachments, send_email=True, | ||||
|                     doctype='Sales Invoice', name=name) | ||||
| 		name_list.append(key) | ||||
| 
 | ||||
| 	return name_list | ||||
| 
 | ||||
| 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.stock_uom = item.get('stock_uom') | ||||
| 			item_doc.uom = item.get('uom') | ||||
| 			item_doc.item_group = item.get('item_group') | ||||
| 			item_doc.append('item_defaults', { | ||||
| 				"company": doc.get("company"), | ||||
| 				"default_warehouse": item.get('warehouse') | ||||
| 			}) | ||||
| 			item_doc.save(ignore_permissions=True) | ||||
| 			frappe.db.commit() | ||||
| 
 | ||||
| def submit_invoice(si_doc, name, doc, name_list): | ||||
| 	try: | ||||
| 		si_doc.insert() | ||||
| 		si_doc.submit() | ||||
| 		frappe.db.commit() | ||||
| 		name_list.append(name) | ||||
| 	except Exception as e: | ||||
| 		if frappe.message_log: | ||||
| 			frappe.message_log.pop() | ||||
| 		frappe.db.rollback() | ||||
| 		frappe.log_error(frappe.get_traceback()) | ||||
| 		name_list = save_invoice(doc, name, name_list) | ||||
| 
 | ||||
| 	return name_list | ||||
| 
 | ||||
| def save_invoice(doc, name, name_list): | ||||
| 	try: | ||||
| 		if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): | ||||
| 			si = frappe.new_doc('Sales Invoice') | ||||
| 			si.update(doc) | ||||
| 			si.set_posting_time = 1 | ||||
| 			si.customer = get_customer_id(doc) | ||||
| 			si.due_date = doc.get('posting_date') | ||||
| 			si.flags.ignore_mandatory = True | ||||
| 			si.insert(ignore_permissions=True) | ||||
| 			frappe.db.commit() | ||||
| 			name_list.append(name) | ||||
| 	except Exception: | ||||
| 		frappe.db.rollback() | ||||
| 		frappe.log_error(frappe.get_traceback()) | ||||
| 
 | ||||
| 	return name_list | ||||
| @ -282,7 +282,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte | ||||
| 					"customer": this.frm.doc.customer | ||||
| 				}, | ||||
| 				callback: function(r) { | ||||
| 					if(r.message && r.message.length) { | ||||
| 					if(r.message && r.message.length > 1) { | ||||
| 						select_loyalty_program(me.frm, r.message); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_import": 1, | ||||
|  "allow_workflow": 1, | ||||
|  "autoname": "naming_series:", | ||||
|  "creation": "2013-05-24 19:29:05", | ||||
|  "doctype": "DocType", | ||||
| @ -13,6 +14,7 @@ | ||||
|   "customer_name", | ||||
|   "tax_id", | ||||
|   "is_pos", | ||||
|   "is_consolidated", | ||||
|   "pos_profile", | ||||
|   "offline_pos_name", | ||||
|   "is_return", | ||||
| @ -1158,6 +1160,7 @@ | ||||
|    "hide_days": 1, | ||||
|    "hide_seconds": 1, | ||||
|    "label": "In Words (Company Currency)", | ||||
|    "length": 240, | ||||
|    "oldfieldname": "in_words", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
| @ -1215,6 +1218,7 @@ | ||||
|    "hide_days": 1, | ||||
|    "hide_seconds": 1, | ||||
|    "label": "In Words", | ||||
|    "length": 240, | ||||
|    "oldfieldname": "in_words_export", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
| @ -1921,6 +1925,13 @@ | ||||
|    "hide_days": 1, | ||||
|    "hide_seconds": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "is_consolidated", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Consolidated", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fetch_from": "customer.is_internal_customer", | ||||
| @ -1936,7 +1947,7 @@ | ||||
|  "idx": 181, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-06-30 12:00:03.890180", | ||||
|  "modified": "2020-07-18 05:07:16.725974", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice", | ||||
|  | ||||
| @ -8,8 +8,6 @@ from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_d | ||||
| from frappe import _, msgprint, throw | ||||
| from erpnext.accounts.party import get_party_account, get_due_date | ||||
| from frappe.model.mapper import get_mapped_doc | ||||
| from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option | ||||
| 
 | ||||
| from erpnext.controllers.selling_controller import SellingController | ||||
| from erpnext.accounts.utils import get_account_currency | ||||
| from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so | ||||
| @ -133,7 +131,7 @@ class SalesInvoice(SellingController): | ||||
| 		if self.is_pos and self.is_return: | ||||
| 			self.verify_payment_amount_is_negative() | ||||
| 
 | ||||
| 		if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: | ||||
| 		if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated: | ||||
| 			validate_loyalty_points(self, self.loyalty_points) | ||||
| 
 | ||||
| 	def validate_fixed_asset(self): | ||||
| @ -200,13 +198,13 @@ class SalesInvoice(SellingController): | ||||
| 		update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) | ||||
| 
 | ||||
| 		# create the loyalty point ledger entry if the customer is enrolled in any loyalty program | ||||
| 		if not self.is_return and self.loyalty_program: | ||||
| 		if not self.is_return and not self.is_consolidated and self.loyalty_program: | ||||
| 			self.make_loyalty_point_entry() | ||||
| 		elif self.is_return and self.return_against and self.loyalty_program: | ||||
| 		elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program: | ||||
| 			against_si_doc = frappe.get_doc("Sales Invoice", self.return_against) | ||||
| 			against_si_doc.delete_loyalty_point_entry() | ||||
| 			against_si_doc.make_loyalty_point_entry() | ||||
| 		if self.redeem_loyalty_points and self.loyalty_points: | ||||
| 		if self.redeem_loyalty_points and not self.is_consolidated and self.loyalty_points: | ||||
| 			self.apply_loyalty_points() | ||||
| 
 | ||||
| 		# Healthcare Service Invoice. | ||||
| @ -265,9 +263,9 @@ class SalesInvoice(SellingController): | ||||
| 		if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction": | ||||
| 			update_company_current_month_sales(self.company) | ||||
| 			self.update_project() | ||||
| 		if not self.is_return and self.loyalty_program: | ||||
| 		if not self.is_return and not self.is_consolidated and self.loyalty_program: | ||||
| 			self.delete_loyalty_point_entry() | ||||
| 		elif self.is_return and self.return_against and self.loyalty_program: | ||||
| 		elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program: | ||||
| 			against_si_doc = frappe.get_doc("Sales Invoice", self.return_against) | ||||
| 			against_si_doc.delete_loyalty_point_entry() | ||||
| 			against_si_doc.make_loyalty_point_entry() | ||||
| @ -347,7 +345,7 @@ class SalesInvoice(SellingController): | ||||
| 
 | ||||
| 		super(SalesInvoice, self).set_missing_values(for_validate) | ||||
| 
 | ||||
| 		print_format = pos.get("print_format_for_online") if pos else None | ||||
| 		print_format = pos.get("print_format") if pos else None | ||||
| 		if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')): | ||||
| 			print_format = 'POS Invoice' | ||||
| 
 | ||||
| @ -420,8 +418,6 @@ class SalesInvoice(SellingController): | ||||
| 			self.account_for_change_amount = frappe.get_cached_value('Company',  self.company,  'default_cash_account') | ||||
| 
 | ||||
| 		if pos: | ||||
| 			self.allow_print_before_pay = pos.allow_print_before_pay | ||||
| 
 | ||||
| 			if not for_validate: | ||||
| 				self.tax_category = pos.get("tax_category") | ||||
| 
 | ||||
| @ -432,8 +428,8 @@ class SalesInvoice(SellingController): | ||||
| 			if pos.get('account_for_change_amount'): | ||||
| 				self.account_for_change_amount = pos.get('account_for_change_amount') | ||||
| 
 | ||||
| 			for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name', | ||||
| 				'company', 'select_print_heading', 'cash_bank_account', 'write_off_account', 'taxes_and_charges', | ||||
| 			for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name', | ||||
| 				'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', | ||||
| 				'write_off_cost_center', 'apply_discount_on', 'cost_center'): | ||||
| 					if (not for_validate) or (for_validate and not self.get(fieldname)): | ||||
| 						self.set(fieldname, pos.get(fieldname)) | ||||
| @ -1123,7 +1119,8 @@ class SalesInvoice(SellingController): | ||||
| 				"loyalty_program": lp_details.loyalty_program, | ||||
| 				"loyalty_program_tier": lp_details.tier_name, | ||||
| 				"customer": self.customer, | ||||
| 				"sales_invoice": self.name, | ||||
| 				"invoice_type": self.doctype, | ||||
| 				"invoice": self.name, | ||||
| 				"loyalty_points": points_earned, | ||||
| 				"purchase_amount": eligible_amount, | ||||
| 				"expiry_date": add_days(self.posting_date, lp_details.expiry_duration), | ||||
| @ -1135,18 +1132,18 @@ class SalesInvoice(SellingController): | ||||
| 
 | ||||
| 	# valdite the redemption and then delete the loyalty points earned on cancel of the invoice | ||||
| 	def delete_loyalty_point_entry(self): | ||||
| 		lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where sales_invoice=%s", | ||||
| 		lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where invoice=%s", | ||||
| 			(self.name), as_dict=1) | ||||
| 
 | ||||
| 		if not lp_entry: return | ||||
| 		against_lp_entry = frappe.db.sql('''select name, sales_invoice from `tabLoyalty Point Entry` | ||||
| 		against_lp_entry = frappe.db.sql('''select name, invoice from `tabLoyalty Point Entry` | ||||
| 			where redeem_against=%s''', (lp_entry[0].name), as_dict=1) | ||||
| 		if against_lp_entry: | ||||
| 			invoice_list = ", ".join([d.sales_invoice for d in against_lp_entry]) | ||||
| 			frappe.throw(_('''Sales Invoice can't be cancelled since the Loyalty Points earned has been redeemed. | ||||
| 				First cancel the Sales Invoice No {0}''').format(invoice_list)) | ||||
| 			invoice_list = ", ".join([d.invoice for d in against_lp_entry]) | ||||
| 			frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. | ||||
| 				First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list)) | ||||
| 		else: | ||||
| 			frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name)) | ||||
| 			frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name)) | ||||
| 			# Set loyalty program | ||||
| 			self.set_loyalty_program_tier() | ||||
| 
 | ||||
| @ -1172,7 +1169,9 @@ class SalesInvoice(SellingController): | ||||
| 
 | ||||
| 		points_to_redeem = self.loyalty_points | ||||
| 		for lp_entry in loyalty_point_entries: | ||||
| 			if lp_entry.sales_invoice == self.name: | ||||
| 			if lp_entry.invoice_type != self.doctype or lp_entry.invoice == self.name: | ||||
| 				# redeemption should be done against same doctype | ||||
| 				# also it shouldn't be against itself | ||||
| 				continue | ||||
| 			available_points = lp_entry.loyalty_points - flt(redemption_details.get(lp_entry.name)) | ||||
| 			if available_points > points_to_redeem: | ||||
| @ -1185,7 +1184,8 @@ class SalesInvoice(SellingController): | ||||
| 				"loyalty_program": self.loyalty_program, | ||||
| 				"loyalty_program_tier": lp_entry.loyalty_program_tier, | ||||
| 				"customer": self.customer, | ||||
| 				"sales_invoice": self.name, | ||||
| 				"invoice_type": self.doctype, | ||||
| 				"invoice": self.name, | ||||
| 				"redeem_against": lp_entry.name, | ||||
| 				"loyalty_points": -1*redeemed_points, | ||||
| 				"purchase_amount": self.grand_total, | ||||
| @ -1576,13 +1576,13 @@ def get_loyalty_programs(customer): | ||||
| 	from erpnext.selling.doctype.customer.customer import get_loyalty_programs | ||||
| 
 | ||||
| 	customer = frappe.get_doc('Customer', customer) | ||||
| 	if customer.loyalty_program: return | ||||
| 	if customer.loyalty_program: return [customer.loyalty_program] | ||||
| 
 | ||||
| 	lp_details = get_loyalty_programs(customer) | ||||
| 
 | ||||
| 	if len(lp_details) == 1: | ||||
| 		frappe.db.set(customer, 'loyalty_program', lp_details[0]) | ||||
| 		return [] | ||||
| 		return lp_details | ||||
| 	else: | ||||
| 		return lp_details | ||||
| 
 | ||||
| @ -1603,7 +1603,41 @@ def create_invoice_discounting(source_name, target_doc=None): | ||||
| 
 | ||||
| 	return invoice_discounting | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def update_multi_mode_option(doc, pos_profile): | ||||
| 	def append_payment(payment_mode): | ||||
| 		payment = doc.append('payments', {}) | ||||
| 		payment.default = payment_mode.default | ||||
| 		payment.mode_of_payment = payment_mode.parent | ||||
| 		payment.account = payment_mode.default_account | ||||
| 		payment.type = payment_mode.type | ||||
| 
 | ||||
| 	doc.set('payments', []) | ||||
| 	if not pos_profile or not pos_profile.get('payments'): | ||||
| 		for payment_mode in get_all_mode_of_payments(doc): | ||||
| 			append_payment(payment_mode) | ||||
| 		return | ||||
| 
 | ||||
| 	for pos_payment_method in pos_profile.get('payments'): | ||||
| 		pos_payment_method = pos_payment_method.as_dict() | ||||
| 		 | ||||
| 		payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) | ||||
| 		payment_mode[0].default = pos_payment_method.default | ||||
| 		append_payment(payment_mode[0]) | ||||
| 
 | ||||
| def get_all_mode_of_payments(doc): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type  | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp  | ||||
| 		where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", | ||||
| 	{'company': doc.company}, as_dict=1) | ||||
| 
 | ||||
| def get_mode_of_payment_info(mode_of_payment, company): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type  | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp  | ||||
| 		where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""", | ||||
| 	(company, mode_of_payment), as_dict=1) | ||||
| 
 | ||||
| def create_dunning(source_name, target_doc=None): | ||||
| 	from frappe.model.mapper import get_mapped_doc | ||||
| 	from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount | ||||
| @ -1635,4 +1669,4 @@ def create_dunning(source_name, target_doc=None): | ||||
| 			"doctype": "Dunning", | ||||
| 		} | ||||
| 	}, target_doc, set_missing_values) | ||||
| 	return doclist | ||||
| 	return doclist | ||||
|  | ||||
| @ -706,37 +706,15 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 
 | ||||
| 		self.pos_gl_entry(si, pos, 50) | ||||
| 
 | ||||
| 	def test_pos_returns_without_repayment(self): | ||||
| 		pos_profile = make_pos_profile() | ||||
| 
 | ||||
| 		pos = create_sales_invoice(qty = 10, do_not_save=True) | ||||
| 		pos.is_pos = 1 | ||||
| 		pos.pos_profile = pos_profile.name | ||||
| 
 | ||||
| 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500}) | ||||
| 		pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500}) | ||||
| 		pos.insert() | ||||
| 		pos.submit() | ||||
| 
 | ||||
| 		pos_return = create_sales_invoice(is_return=1, | ||||
| 			return_against=pos.name, qty=-5, do_not_save=True) | ||||
| 
 | ||||
| 		pos_return.is_pos = 1 | ||||
| 		pos_return.pos_profile = pos_profile.name | ||||
| 
 | ||||
| 		pos_return.insert() | ||||
| 		pos_return.submit() | ||||
| 
 | ||||
| 		self.assertFalse(pos_return.is_pos) | ||||
| 		self.assertFalse(pos_return.get('payments')) | ||||
| 
 | ||||
| 	def test_pos_returns_with_repayment(self): | ||||
| 		from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return | ||||
| 
 | ||||
| 		pos_profile = make_pos_profile() | ||||
| 
 | ||||
| 		pos_profile.payments = [] | ||||
| 		pos_profile.append('payments', { | ||||
| 			'default': 1, | ||||
| 			'mode_of_payment': 'Cash', | ||||
| 			'amount': 0.0 | ||||
| 			'mode_of_payment': 'Cash' | ||||
| 		}) | ||||
| 
 | ||||
| 		pos_profile.save() | ||||
| @ -751,18 +729,12 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		pos.insert() | ||||
| 		pos.submit() | ||||
| 
 | ||||
| 		pos_return = create_sales_invoice(is_return=1, | ||||
| 			return_against=pos.name, qty=-5, do_not_save=True) | ||||
| 		pos_return = make_sales_return(pos.name) | ||||
| 
 | ||||
| 		pos_return.is_pos = 1 | ||||
| 		pos_return.pos_profile = pos_profile.name | ||||
| 		pos_return.insert() | ||||
| 		pos_return.submit() | ||||
| 
 | ||||
| 		self.assertEqual(pos_return.get('payments')[0].amount, -500) | ||||
| 		pos_profile.payments = [] | ||||
| 		pos_profile.save() | ||||
| 
 | ||||
| 		self.assertEqual(pos_return.get('payments')[0].amount, -1000) | ||||
| 
 | ||||
| 	def test_pos_change_amount(self): | ||||
| 		make_pos_profile() | ||||
| @ -788,82 +760,6 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		self.assertEqual(pos.grand_total, 100.0) | ||||
| 		self.assertEqual(pos.write_off_amount, -5) | ||||
| 
 | ||||
| 	def test_make_pos_invoice(self): | ||||
| 		from erpnext.accounts.doctype.sales_invoice.pos import make_invoice | ||||
| 
 | ||||
| 		pos_profile = make_pos_profile() | ||||
| 
 | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", | ||||
| 			item_code= "_Test FG Item", | ||||
| 			warehouse= "Stores - TCP1", cost_center= "Main - TCP1") | ||||
| 
 | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", | ||||
| 			debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", | ||||
| 			income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", | ||||
| 			cost_center = "Main - TCP1", do_not_save=True) | ||||
| 
 | ||||
| 		pos.is_pos = 1 | ||||
| 		pos.update_stock = 1 | ||||
| 
 | ||||
| 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) | ||||
| 		pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50}) | ||||
| 
 | ||||
| 		taxes = get_taxes_and_charges() | ||||
| 		pos.taxes = [] | ||||
| 		for tax in taxes: | ||||
| 			pos.append("taxes", tax) | ||||
| 
 | ||||
| 		invoice_data = [{'09052016142': pos}] | ||||
| 		si = make_invoice(pos_profile, invoice_data).get('invoice') | ||||
| 		self.assertEqual(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.assertEqual(si.grand_total, 100) | ||||
| 
 | ||||
| 		self.pos_gl_entry(si, pos, 50) | ||||
| 
 | ||||
| 	def test_make_pos_invoice_in_draft(self): | ||||
| 		from erpnext.accounts.doctype.sales_invoice.pos import make_invoice | ||||
| 		from erpnext.stock.doctype.item.test_item import make_item | ||||
| 
 | ||||
| 		allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') | ||||
| 		if allow_negative_stock: | ||||
| 			frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) | ||||
| 
 | ||||
| 		pos_profile = make_pos_profile() | ||||
| 		timestamp = cint(time.time()) | ||||
| 
 | ||||
| 		item = make_item("_Test POS Item") | ||||
| 		pos = copy.deepcopy(test_records[1]) | ||||
| 		pos['items'][0]['item_code'] = item.name | ||||
| 		pos['items'][0]['warehouse'] = "_Test Warehouse - _TC" | ||||
| 		pos["is_pos"] = 1 | ||||
| 		pos["offline_pos_name"] = timestamp | ||||
| 		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 = [{timestamp: pos}] | ||||
| 		si = make_invoice(pos_profile, invoice_data).get('invoice') | ||||
| 		self.assertEqual(si[0], timestamp) | ||||
| 
 | ||||
| 		sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp}) | ||||
| 		self.assertEqual(sales_invoice[0].docstatus, 0) | ||||
| 
 | ||||
| 		timestamp = cint(time.time()) | ||||
| 		pos["offline_pos_name"] = timestamp | ||||
| 		invoice_data = [{timestamp: pos}] | ||||
| 		si1 = make_invoice(pos_profile, invoice_data).get('invoice') | ||||
| 		self.assertEqual(si1[0], timestamp) | ||||
| 
 | ||||
| 		sales_invoice1 = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp}) | ||||
| 		self.assertEqual(sales_invoice1[0].docstatus, 0) | ||||
| 
 | ||||
| 		if allow_negative_stock: | ||||
| 			frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1) | ||||
| 
 | ||||
| 	def pos_gl_entry(self, si, pos, cash_amount): | ||||
| 		# check stock ledger entries | ||||
| 		sle = frappe.db.sql("""select * from `tabStock Ledger Entry` | ||||
|  | ||||
| @ -1,314 +1,90 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_events_in_timeline": 0,  | ||||
|  "allow_guest_to_view": 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": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "actions": [], | ||||
|  "creation": "2016-05-08 23:49:38.842621", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "default", | ||||
|   "mode_of_payment", | ||||
|   "amount", | ||||
|   "column_break_3", | ||||
|   "account", | ||||
|   "type", | ||||
|   "base_amount", | ||||
|   "clearance_date" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "depends_on": "eval:parent.doctype == 'POS Profile'",  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "default",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "mode_of_payment", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Mode of Payment", | ||||
|    "options": "Mode of Payment", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "mode_of_payment",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "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,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:parent.doctype == 'Sales Invoice'", | ||||
|    "fieldname": "amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Amount", | ||||
|    "options": "currency", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "0",  | ||||
|    "depends_on": "eval:parent.doctype == 'Sales Invoice'",  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Amount",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "column_break_3",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Account", | ||||
|    "options": "Account", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "account",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Account",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Account",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 1,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fetch_from": "mode_of_payment.type", | ||||
|    "fieldname": "type", | ||||
|    "fieldtype": "Read Only", | ||||
|    "label": "Type" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_from": "mode_of_payment.type",  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "type",  | ||||
|    "fieldtype": "Read Only",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Type",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "base_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Base Amount (Company Currency)", | ||||
|    "no_copy": 1, | ||||
|    "options": "Company:company:default_currency", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "base_amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "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,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "clearance_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Clearance Date", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "clearance_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Clearance Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 1,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "default": "0", | ||||
|    "fieldname": "default", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 1, | ||||
|    "label": "Default", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2019-03-19 14:54:56.524556",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Sales Invoice Payment",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 0,  | ||||
|  "track_seen": 0,  | ||||
|  "track_views": 0 | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-05 16:51:20.091441", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice Payment", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -158,8 +158,10 @@ def validate_account_for_perpetual_inventory(gl_map): | ||||
| 			if account not in aii_accounts: | ||||
| 				continue | ||||
| 
 | ||||
| 			# Always use current date to get stock and account balance as there can future entries for | ||||
| 			# other items | ||||
| 			account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, | ||||
| 				gl_map[0].posting_date, gl_map[0].company) | ||||
| 				getdate(), gl_map[0].company) | ||||
| 
 | ||||
| 			if gl_map[0].voucher_type=="Journal Entry": | ||||
| 				# In case of Journal Entry, there are no corresponding SL entries, | ||||
| @ -169,7 +171,6 @@ def validate_account_for_perpetual_inventory(gl_map): | ||||
| 					frappe.throw(_("Account: {0} can only be updated via Stock Transactions") | ||||
| 						.format(account), StockAccountInvalidTransaction) | ||||
| 
 | ||||
| 			# This has been comment for a temporary, will add this code again on release of immutable ledger | ||||
| 			elif account_bal != stock_bal: | ||||
| 				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||
| 					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,28 +0,0 @@ | ||||
| { | ||||
|  "content": null,  | ||||
|  "creation": "2014-08-08 02:45:55.931022",  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "Page",  | ||||
|  "icon": "fa fa-th",  | ||||
|  "modified": "2014-08-08 05:59:33.045012",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "pos",  | ||||
|  "owner": "Administrator",  | ||||
|  "page_name": "pos",  | ||||
|  "roles": [ | ||||
|   { | ||||
|    "role": "Sales User" | ||||
|   },  | ||||
|   { | ||||
|    "role": "Purchase User" | ||||
|   },  | ||||
|   { | ||||
|    "role": "Accounts User" | ||||
|   } | ||||
|  ],  | ||||
|  "script": null,  | ||||
|  "standard": "Yes",  | ||||
|  "style": null,  | ||||
|  "title": "POS" | ||||
| } | ||||
| @ -1,52 +0,0 @@ | ||||
| QUnit.test("test:Sales Invoice", function(assert) { | ||||
| 	assert.expect(3); | ||||
| 	let done = assert.async(); | ||||
| 
 | ||||
| 	frappe.run_serially([ | ||||
| 		() => { | ||||
| 			return frappe.tests.make("POS Profile", [ | ||||
| 				{naming_series: "SINV"}, | ||||
| 				{pos_profile_name: "_Test POS Profile"}, | ||||
| 				{country: "India"}, | ||||
| 				{currency: "INR"}, | ||||
| 				{write_off_account: "Write Off - FT"}, | ||||
| 				{write_off_cost_center: "Main - FT"}, | ||||
| 				{payments: [ | ||||
| 					[ | ||||
| 						{"default": 1}, | ||||
| 						{"mode_of_payment": "Cash"} | ||||
| 					]] | ||||
| 				} | ||||
| 			]); | ||||
| 		}, | ||||
| 		() => cur_frm.save(), | ||||
| 		() => frappe.timeout(2), | ||||
| 		() => { | ||||
| 			assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested"); | ||||
| 		}, | ||||
| 		() => frappe.timeout(1), | ||||
| 		() => { | ||||
| 			return frappe.tests.make("Sales Invoice", [ | ||||
| 				{customer: "Test Customer 2"}, | ||||
| 				{is_pos: 1}, | ||||
| 				{posting_date: frappe.datetime.get_today()}, | ||||
| 				{due_date: frappe.datetime.get_today()}, | ||||
| 				{items: [ | ||||
| 					[ | ||||
| 						{"item_code": "Test Product 1"}, | ||||
| 						{"qty": 5}, | ||||
| 						{"warehouse":'Stores - FT'} | ||||
| 					]] | ||||
| 				} | ||||
| 			]); | ||||
| 		}, | ||||
| 		() => frappe.timeout(2), | ||||
| 		() => cur_frm.save(), | ||||
| 		() => frappe.timeout(2), | ||||
| 		() => { | ||||
| 			assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested"); | ||||
| 			assert.equal(cur_frm.doc.payments[0].mode_of_payment, "Cash", "Default mode of payment tested"); | ||||
| 		}, | ||||
| 		() => done() | ||||
| 	]); | ||||
| }); | ||||
| @ -184,7 +184,7 @@ def set_price_list(party_details, party, party_type, given_price_list, pos=None) | ||||
| 
 | ||||
| 
 | ||||
| def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype): | ||||
| 	if doctype not in ["Sales Invoice", "Purchase Invoice"]: | ||||
| 	if doctype not in ["POS Invoice", "Sales Invoice", "Purchase Invoice"]: | ||||
| 		# not an invoice | ||||
| 		return { | ||||
| 			party_type.lower(): party | ||||
|  | ||||
| @ -7,10 +7,10 @@ | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Print Format", | ||||
|  "font": "Default", | ||||
|  "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"40%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>", | ||||
|  "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>", | ||||
|  "idx": 0, | ||||
|  "line_breaks": 0, | ||||
|  "modified": "2019-12-09 17:39:23.356573", | ||||
|  "modified": "2020-04-29 16:39:12.936215", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "GST POS Invoice", | ||||
|  | ||||
| @ -6,10 +6,10 @@ | ||||
|  "doc_type": "Sales Invoice", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Print Format", | ||||
|  "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>", | ||||
|  "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>", | ||||
|  "idx": 1, | ||||
|  "line_breaks": 0, | ||||
|  "modified": "2019-12-09 17:40:53.183574", | ||||
|  "modified": "2020-04-29 16:35:07.043058", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Invoice", | ||||
|  | ||||
| @ -223,9 +223,9 @@ class GrossProfitGenerator(object): | ||||
| 		# IMP NOTE | ||||
| 		# stock_ledger_entries should already be filtered by item_code and warehouse and | ||||
| 		# sorted by posting_date desc, posting_time desc | ||||
| 		if item_code in self.non_stock_items: | ||||
| 		if item_code in self.non_stock_items and (row.project or row.cost_center): | ||||
| 			#Issue 6089-Get last purchasing rate for non-stock item | ||||
| 			item_rate = self.get_last_purchase_rate(item_code) | ||||
| 			item_rate = self.get_last_purchase_rate(item_code, row) | ||||
| 			return flt(row.qty) * item_rate | ||||
| 
 | ||||
| 		else: | ||||
| @ -253,38 +253,34 @@ class GrossProfitGenerator(object): | ||||
| 	def get_average_buying_rate(self, row, item_code): | ||||
| 		args = row | ||||
| 		if not item_code in self.average_buying_rate: | ||||
| 			if item_code in self.non_stock_items: | ||||
| 				self.average_buying_rate[item_code] = flt(frappe.db.sql(""" | ||||
| 					select sum(base_net_amount) / sum(qty * conversion_factor) | ||||
| 					from `tabPurchase Invoice Item` | ||||
| 					where item_code = %s and docstatus=1""", item_code)[0][0]) | ||||
| 			else: | ||||
| 				args.update({ | ||||
| 					'voucher_type': row.parenttype, | ||||
| 					'voucher_no': row.parent, | ||||
| 					'allow_zero_valuation': True, | ||||
| 					'company': self.filters.company | ||||
| 				}) | ||||
| 			args.update({ | ||||
| 				'voucher_type': row.parenttype, | ||||
| 				'voucher_no': row.parent, | ||||
| 				'allow_zero_valuation': True, | ||||
| 				'company': self.filters.company | ||||
| 			}) | ||||
| 
 | ||||
| 				average_buying_rate = get_incoming_rate(args) | ||||
| 				self.average_buying_rate[item_code] =  flt(average_buying_rate) | ||||
| 			average_buying_rate = get_incoming_rate(args) | ||||
| 			self.average_buying_rate[item_code] =  flt(average_buying_rate) | ||||
| 
 | ||||
| 		return self.average_buying_rate[item_code] | ||||
| 
 | ||||
| 	def get_last_purchase_rate(self, item_code): | ||||
| 	def get_last_purchase_rate(self, item_code, row): | ||||
| 		condition = '' | ||||
| 		if row.project: | ||||
| 			condition += " AND a.project='%s'" % (row.project) | ||||
| 		elif row.cost_center: | ||||
| 			condition += " AND a.cost_center='%s'" % (row.cost_center) | ||||
| 		if self.filters.to_date: | ||||
| 			last_purchase_rate = frappe.db.sql(""" | ||||
| 			select (a.base_rate / a.conversion_factor) | ||||
| 			from `tabPurchase Invoice Item` a | ||||
| 			where a.item_code = %s and a.docstatus=1 | ||||
| 			and modified <= %s | ||||
| 			order by a.modified desc limit 1""", (item_code, self.filters.to_date)) | ||||
| 		else: | ||||
| 			last_purchase_rate = frappe.db.sql(""" | ||||
| 			select (a.base_rate / a.conversion_factor) | ||||
| 			from `tabPurchase Invoice Item` a | ||||
| 			where a.item_code = %s and a.docstatus=1 | ||||
| 			order by a.modified desc limit 1""", item_code) | ||||
| 			condition += " AND modified='%s'" % (self.filters.to_date) | ||||
| 
 | ||||
| 		last_purchase_rate = frappe.db.sql(""" | ||||
| 		select (a.base_rate / a.conversion_factor) | ||||
| 		from `tabPurchase Invoice Item` a | ||||
| 		where a.item_code = %s and a.docstatus=1 | ||||
| 		{0} | ||||
| 		order by a.modified desc limit 1""".format(condition), item_code) | ||||
| 
 | ||||
| 		return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0 | ||||
| 
 | ||||
| 	def load_invoice_items(self): | ||||
| @ -321,7 +317,8 @@ class GrossProfitGenerator(object): | ||||
| 				`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail, | ||||
| 				`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty, | ||||
| 				`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, | ||||
| 				`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return | ||||
| 				`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return, | ||||
| 				`tabSales Invoice Item`.cost_center | ||||
| 				{sales_person_cols} | ||||
| 			from | ||||
| 				`tabSales Invoice` inner join `tabSales Invoice Item` | ||||
|  | ||||
							
								
								
									
										39
									
								
								erpnext/assets/assets_dashboard/asset/asset.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								erpnext/assets/assets_dashboard/asset/asset.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| { | ||||
|  "cards": [ | ||||
|   { | ||||
|    "card": "Total Assets" | ||||
|   }, | ||||
|   { | ||||
|    "card": "New Assets (This Year)" | ||||
|   }, | ||||
|   { | ||||
|    "card": "Asset Value" | ||||
|   } | ||||
|  ], | ||||
|  "charts": [ | ||||
|   { | ||||
|    "chart": "Asset Value Analytics", | ||||
|    "width": "Full" | ||||
|   }, | ||||
|   { | ||||
|    "chart": "Category-wise Asset Value", | ||||
|    "width": "Half" | ||||
|   }, | ||||
|   { | ||||
|    "chart": "Location-wise Asset Value", | ||||
|    "width": "Half" | ||||
|   } | ||||
|  ], | ||||
|  "creation": "2020-07-14 18:23:53.343082", | ||||
|  "dashboard_name": "Asset", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Dashboard", | ||||
|  "idx": 0, | ||||
|  "is_default": 0, | ||||
|  "is_standard": 1, | ||||
|  "modified": "2020-07-21 18:14:25.078929", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Asset", | ||||
|  "owner": "Administrator" | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| { | ||||
|  "chart_name": "Asset Value Analytics", | ||||
|  "chart_type": "Report", | ||||
|  "creation": "2020-07-14 18:23:53.091233", | ||||
|  "custom_options": "{\"type\": \"bar\", \"barOptions\": {\"stacked\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}}", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Dashboard Chart", | ||||
|  "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", | ||||
|  "filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}", | ||||
|  "group_by_type": "Count", | ||||
|  "idx": 0, | ||||
|  "is_public": 0, | ||||
|  "is_standard": 1, | ||||
|  "modified": "2020-07-23 13:53:33.211371", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Asset Value Analytics", | ||||
|  "number_of_groups": 0, | ||||
|  "owner": "Administrator", | ||||
|  "report_name": "Fixed Asset Register", | ||||
|  "time_interval": "Yearly", | ||||
|  "timeseries": 0, | ||||
|  "timespan": "Last Year", | ||||
|  "type": "Bar", | ||||
|  "use_report_chart": 1, | ||||
|  "y_axis": [] | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| { | ||||
|  "chart_name": "Category-wise Asset Value", | ||||
|  "chart_type": "Report", | ||||
|  "creation": "2020-07-14 18:23:53.146304", | ||||
|  "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Dashboard Chart", | ||||
|  "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", | ||||
|  "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}", | ||||
|  "idx": 0, | ||||
|  "is_public": 0, | ||||
|  "is_standard": 1, | ||||
|  "modified": "2020-07-23 13:39:32.429240", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Category-wise Asset Value", | ||||
|  "number_of_groups": 0, | ||||
|  "owner": "Administrator", | ||||
|  "report_name": "Fixed Asset Register", | ||||
|  "timeseries": 0, | ||||
|  "type": "Donut", | ||||
|  "use_report_chart": 0, | ||||
|  "x_field": "asset_category", | ||||
|  "y_axis": [ | ||||
|   { | ||||
|    "y_field": "asset_value" | ||||
|   } | ||||
|  ] | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| { | ||||
|  "chart_name": "Location-wise Asset Value", | ||||
|  "chart_type": "Report", | ||||
|  "creation": "2020-07-14 18:23:53.195389", | ||||
|  "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Dashboard Chart", | ||||
|  "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", | ||||
|  "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}", | ||||
|  "idx": 0, | ||||
|  "is_public": 0, | ||||
|  "is_standard": 1, | ||||
|  "modified": "2020-07-23 13:42:44.912551", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Location-wise Asset Value", | ||||
|  "number_of_groups": 0, | ||||
|  "owner": "Administrator", | ||||
|  "report_name": "Fixed Asset Register", | ||||
|  "timeseries": 0, | ||||
|  "type": "Donut", | ||||
|  "use_report_chart": 0, | ||||
|  "x_field": "location", | ||||
|  "y_axis": [ | ||||
|   { | ||||
|    "y_field": "asset_value" | ||||
|   } | ||||
|  ] | ||||
| } | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_import": 1, | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "naming_series:", | ||||
| @ -7,8 +8,9 @@ | ||||
|  "document_type": "Document", | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "is_existing_asset", | ||||
|   "section_break_2", | ||||
|   "naming_series", | ||||
|   "asset_name", | ||||
|   "item_code", | ||||
|   "item_name", | ||||
|   "asset_category", | ||||
| @ -17,29 +19,31 @@ | ||||
|   "supplier", | ||||
|   "customer", | ||||
|   "image", | ||||
|   "purchase_invoice", | ||||
|   "journal_entry_for_scrap", | ||||
|   "column_break_3", | ||||
|   "company", | ||||
|   "asset_name", | ||||
|   "location", | ||||
|   "custodian", | ||||
|   "department", | ||||
|   "purchase_date", | ||||
|   "disposal_date", | ||||
|   "journal_entry_for_scrap", | ||||
|   "purchase_receipt", | ||||
|   "accounting_dimensions_section", | ||||
|   "cost_center", | ||||
|   "dimension_col_break", | ||||
|   "section_break_5", | ||||
|   "gross_purchase_amount", | ||||
|   "purchase_details_section", | ||||
|   "purchase_receipt", | ||||
|   "purchase_invoice", | ||||
|   "available_for_use_date", | ||||
|   "column_break_18", | ||||
|   "column_break_23", | ||||
|   "gross_purchase_amount", | ||||
|   "purchase_date", | ||||
|   "section_break_23", | ||||
|   "calculate_depreciation", | ||||
|   "allow_monthly_depreciation", | ||||
|   "is_existing_asset", | ||||
|   "column_break_33", | ||||
|   "opening_accumulated_depreciation", | ||||
|   "number_of_depreciations_booked", | ||||
|   "section_break_23", | ||||
|   "section_break_36", | ||||
|   "finance_books", | ||||
|   "section_break_33", | ||||
|   "depreciation_method", | ||||
| @ -64,7 +68,6 @@ | ||||
|   "status", | ||||
|   "booked_fixed_asset", | ||||
|   "column_break_51", | ||||
| 
 | ||||
|   "purchase_receipt_amount", | ||||
|   "default_finance_book", | ||||
|   "amended_from" | ||||
| @ -187,6 +190,8 @@ | ||||
|    "fieldname": "purchase_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Purchase Date", | ||||
|    "read_only": 1, | ||||
|    "read_only_depends_on": "eval:!doc.is_existing_asset", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
| @ -204,25 +209,20 @@ | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_5", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "gross_purchase_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Gross Purchase Amount", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1, | ||||
|    "read_only_depends_on": "eval:!doc.is_existing_asset", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "available_for_use_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Available-for-use Date" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_18", | ||||
|    "fieldtype": "Column Break" | ||||
|    "label": "Available-for-use Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
| @ -252,12 +252,14 @@ | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "calculate_depreciation", | ||||
|    "collapsible": 1, | ||||
|    "collapsible_depends_on": "eval:doc.calculate_depreciation || doc.is_existing_asset", | ||||
|    "fieldname": "section_break_23", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Depreciation" | ||||
|   }, | ||||
|   { | ||||
|    "columns": 10, | ||||
|    "fieldname": "finance_books", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Finance Books", | ||||
| @ -305,8 +307,7 @@ | ||||
|   { | ||||
|    "depends_on": "calculate_depreciation", | ||||
|    "fieldname": "section_break_14", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Depreciation Schedule" | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "schedules", | ||||
| @ -456,12 +457,37 @@ | ||||
|    "fieldname": "allow_monthly_depreciation", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Monthly Depreciation" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_2", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "collapsible_depends_on": "is_existing_asset", | ||||
|    "fieldname": "purchase_details_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Purchase Details" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_23", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_33", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "calculate_depreciation", | ||||
|    "fieldname": "section_break_36", | ||||
|    "fieldtype": "Section Break" | ||||
|   } | ||||
|  ], | ||||
|  "idx": 72, | ||||
|  "image_field": "image", | ||||
|  "is_submittable": 1, | ||||
|  "modified": "2019-10-22 15:47:36.050828", | ||||
|  "links": [], | ||||
|  "modified": "2020-07-28 15:04:44.452224", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Asset", | ||||
|  | ||||
							
								
								
									
										21
									
								
								erpnext/assets/number_card/asset_value/asset_value.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								erpnext/assets/number_card/asset_value/asset_value.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| { | ||||
|  "aggregate_function_based_on": "value_after_depreciation", | ||||
|  "creation": "2020-07-14 18:23:53.302457", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Number Card", | ||||
|  "document_type": "Asset", | ||||
|  "filters_json": "[]", | ||||
|  "function": "Sum", | ||||
|  "idx": 0, | ||||
|  "is_public": 1, | ||||
|  "is_standard": 1, | ||||
|  "label": "Asset Value", | ||||
|  "modified": "2020-07-21 18:13:47.647997", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Asset Value", | ||||
|  "owner": "Administrator", | ||||
|  "show_percentage_stats": 1, | ||||
|  "stats_time_interval": "Monthly", | ||||
|  "type": "Document Type" | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| { | ||||
|  "creation": "2020-07-14 18:23:53.267919", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Number Card", | ||||
|  "document_type": "Asset", | ||||
|  "filters_json": "[[\"Asset\",\"creation\",\"Timespan\",\"this year\",false]]", | ||||
|  "function": "Count", | ||||
|  "idx": 0, | ||||
|  "is_public": 1, | ||||
|  "is_standard": 1, | ||||
|  "label": "New Assets (This Year)", | ||||
|  "modified": "2020-07-23 13:45:20.418766", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "New Assets (This Year)", | ||||
|  "owner": "Administrator", | ||||
|  "show_percentage_stats": 1, | ||||
|  "stats_time_interval": "Monthly", | ||||
|  "type": "Document Type" | ||||
| } | ||||
							
								
								
									
										20
									
								
								erpnext/assets/number_card/total_assets/total_assets.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								erpnext/assets/number_card/total_assets/total_assets.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| { | ||||
|  "creation": "2020-07-14 18:23:53.233328", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Number Card", | ||||
|  "document_type": "Asset", | ||||
|  "filters_json": "[]", | ||||
|  "function": "Count", | ||||
|  "idx": 0, | ||||
|  "is_public": 1, | ||||
|  "is_standard": 1, | ||||
|  "label": "Total Assets", | ||||
|  "modified": "2020-07-21 18:12:51.664292", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Assets", | ||||
|  "name": "Total Assets", | ||||
|  "owner": "Administrator", | ||||
|  "show_percentage_stats": 1, | ||||
|  "stats_time_interval": "Monthly", | ||||
|  "type": "Document Type" | ||||
| } | ||||
| @ -7,12 +7,6 @@ frappe.provide("erpnext.buying"); | ||||
| 
 | ||||
| frappe.ui.form.on("Purchase Order", { | ||||
| 	setup: function(frm) { | ||||
| 		frm.custom_make_buttons = { | ||||
| 			'Purchase Receipt': 'Receipt', | ||||
| 			'Purchase Invoice': 'Invoice', | ||||
| 			'Stock Entry': 'Material to Supplier', | ||||
| 			'Payment Entry': 'Payment' | ||||
| 		} | ||||
| 
 | ||||
| 		frm.set_query("reserve_warehouse", "supplied_items", function() { | ||||
| 			return { | ||||
| @ -36,20 +30,6 @@ frappe.ui.form.on("Purchase Order", { | ||||
| 
 | ||||
| 	}, | ||||
| 
 | ||||
| 	refresh: function(frm) { | ||||
| 		if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed' | ||||
| 			&& flt(frm.doc.per_received) < 100 && flt(frm.doc.per_billed) < 100) { | ||||
| 			frm.add_custom_button(__('Update Items'), () => { | ||||
| 				erpnext.utils.update_child_items({ | ||||
| 					frm: frm, | ||||
| 					child_docname: "items", | ||||
| 					child_doctype: "Purchase Order Detail", | ||||
| 					cannot_add_row: false, | ||||
| 				}) | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	onload: function(frm) { | ||||
| 		set_schedule_date(frm); | ||||
| 		if (!frm.doc.transaction_date){ | ||||
| @ -76,6 +56,18 @@ frappe.ui.form.on("Purchase Order Item", { | ||||
| }); | ||||
| 
 | ||||
| erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({ | ||||
| 	setup: function() { | ||||
| 		this.frm.custom_make_buttons = { | ||||
| 			'Purchase Receipt': 'Receipt', | ||||
| 			'Purchase Invoice': 'Invoice', | ||||
| 			'Stock Entry': 'Material to Supplier', | ||||
| 			'Payment Entry': 'Payment', | ||||
| 		} | ||||
| 
 | ||||
| 		this._super(); | ||||
| 
 | ||||
| 	}, | ||||
| 
 | ||||
| 	refresh: function(doc, cdt, cdn) { | ||||
| 		var me = this; | ||||
| 		this._super(); | ||||
| @ -99,6 +91,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( | ||||
| 
 | ||||
| 		if(doc.docstatus == 1) { | ||||
| 			if(!in_list(["Closed", "Delivered"], doc.status)) { | ||||
| 				if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { | ||||
| 					this.frm.add_custom_button(__('Update Items'), () => { | ||||
| 						erpnext.utils.update_child_items({ | ||||
| 							frm: frm, | ||||
| 							child_docname: "items", | ||||
| 							child_doctype: "Purchase Order Detail", | ||||
| 							cannot_add_row: false, | ||||
| 						}) | ||||
| 					}); | ||||
| 				} | ||||
| 				if (this.frm.has_perm("submit")) { | ||||
| 					if(flt(doc.per_billed, 6) < 100 || flt(doc.per_received, 6) < 100) { | ||||
| 						if (doc.status != "On Hold") { | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -97,7 +97,7 @@ | ||||
|   { | ||||
|    "fieldname": "default_bank_account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Default Bank Account", | ||||
|    "label": "Default Company Bank Account", | ||||
|    "options": "Bank Account" | ||||
|   }, | ||||
|   { | ||||
| @ -384,7 +384,7 @@ | ||||
|  "idx": 370, | ||||
|  "image_field": "image", | ||||
|  "links": [], | ||||
|  "modified": "2020-03-17 09:48:30.578242", | ||||
|  "modified": "2020-06-17 23:18:20", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Buying", | ||||
|  "name": "Supplier", | ||||
|  | ||||
| @ -593,6 +593,7 @@ | ||||
|    "fieldname": "base_in_words", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "In Words (Company Currency)", | ||||
|    "length": 240, | ||||
|    "oldfieldname": "in_words", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
| @ -642,6 +643,7 @@ | ||||
|    "fieldname": "in_words", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "In Words", | ||||
|    "length": 240, | ||||
|    "oldfieldname": "in_words_import", | ||||
|    "oldfieldtype": "Data", | ||||
|    "print_hide": 1, | ||||
| @ -803,7 +805,7 @@ | ||||
|  "idx": 29, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-15 21:24:12.639482", | ||||
|  "modified": "2020-07-18 05:10:45.556792", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Buying", | ||||
|  "name": "Supplier Quotation", | ||||
|  | ||||
| @ -213,7 +213,7 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 		doc.return_against = source.name | ||||
| 		doc.ignore_pricing_rule = 1 | ||||
| 		doc.set_warehouse = "" | ||||
| 		if doctype == "Sales Invoice": | ||||
| 		if doctype == "Sales Invoice" or doctype == "POS Invoice": | ||||
| 			doc.is_pos = source.is_pos | ||||
| 
 | ||||
| 			# look for Print Heading "Credit Note" | ||||
| @ -229,7 +229,7 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 				tax.tax_amount = -1 * tax.tax_amount | ||||
| 
 | ||||
| 		if doc.get("is_return"): | ||||
| 			if doc.doctype == 'Sales Invoice': | ||||
| 			if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice': | ||||
| 				doc.set('payments', []) | ||||
| 				for data in source.payments: | ||||
| 					paid_amount = 0.00 | ||||
| @ -241,8 +241,11 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 						'mode_of_payment': data.mode_of_payment, | ||||
| 						'type': data.type, | ||||
| 						'amount': -1 * paid_amount, | ||||
| 						'base_amount': -1 * base_paid_amount | ||||
| 						'base_amount': -1 * base_paid_amount, | ||||
| 						'account': data.account | ||||
| 					}) | ||||
| 				if doc.is_pos: | ||||
| 					doc.paid_amount = -1 * source.paid_amount | ||||
| 			elif doc.doctype == 'Purchase Invoice': | ||||
| 				doc.paid_amount = -1 * source.paid_amount | ||||
| 				doc.base_paid_amount = -1 * source.base_paid_amount | ||||
| @ -287,7 +290,7 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 			target_doc.dn_detail = source_doc.name | ||||
| 			if default_warehouse_for_sales_return: | ||||
| 				target_doc.warehouse = default_warehouse_for_sales_return | ||||
| 		elif doctype == "Sales Invoice": | ||||
| 		elif doctype == "Sales Invoice" or doctype == "POS Invoice": | ||||
| 			target_doc.sales_order = source_doc.sales_order | ||||
| 			target_doc.delivery_note = source_doc.delivery_note | ||||
| 			target_doc.so_detail = source_doc.so_detail | ||||
|  | ||||
| @ -85,6 +85,12 @@ status_map = { | ||||
| 	"Bank Transaction": [ | ||||
| 		["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"], | ||||
| 		["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"] | ||||
| 	], | ||||
| 	"POS Opening Entry": [ | ||||
| 		["Draft", None], | ||||
| 		["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"], | ||||
| 		["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"], | ||||
| 		["Cancelled", "eval:self.docstatus == 2"], | ||||
| 	] | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -370,7 +370,7 @@ class calculate_taxes_and_totals(object): | ||||
| 
 | ||||
| 		self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"]) | ||||
| 
 | ||||
| 		if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: | ||||
| 		if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]: | ||||
| 			self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \ | ||||
| 				if self.doc.total_taxes_and_charges else self.doc.base_net_total | ||||
| 		else: | ||||
| @ -619,17 +619,14 @@ class calculate_taxes_and_totals(object): | ||||
| 		self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc) | ||||
| 
 | ||||
| 	def update_paid_amount_for_return(self, total_amount_to_pay): | ||||
| 		default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment', | ||||
| 			{'parent': self.doc.pos_profile, 'default': 1}, | ||||
| 			['mode_of_payment', 'type', 'account'], as_dict=1) | ||||
| 		default_mode_of_payment = frappe.db.get_value('POS Payment Method', | ||||
| 			{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1) | ||||
| 
 | ||||
| 		self.doc.payments = [] | ||||
| 
 | ||||
| 		if default_mode_of_payment: | ||||
| 			self.doc.append('payments', { | ||||
| 				'mode_of_payment': default_mode_of_payment.mode_of_payment, | ||||
| 				'type': default_mode_of_payment.type, | ||||
| 				'account': default_mode_of_payment.account, | ||||
| 				'amount': total_amount_to_pay | ||||
| 			}) | ||||
| 		else: | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "format:MAIL-CAMP-{YYYY}-{#####}", | ||||
|  "creation": "2019-06-30 16:05:30.015615", | ||||
|  "doctype": "DocType", | ||||
| @ -52,7 +53,7 @@ | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Email Campaign For ", | ||||
|    "options": "\nLead\nContact", | ||||
|    "options": "\nLead\nContact\nEmail Group", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
| @ -70,7 +71,8 @@ | ||||
|    "options": "User" | ||||
|   } | ||||
|  ], | ||||
|  "modified": "2019-11-11 17:18:47.342839", | ||||
|  "links": [], | ||||
|  "modified": "2020-07-15 12:43:25.548682", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "CRM", | ||||
|  "name": "Email Campaign", | ||||
|  | ||||
| @ -70,10 +70,15 @@ def send_email_to_leads_or_contacts(): | ||||
| 				send_mail(entry, email_campaign) | ||||
| 
 | ||||
| def send_mail(entry, email_campaign): | ||||
| 	recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id') | ||||
| 	recipient_list = [] | ||||
| 	if email_campaign.email_campaign_for == "Email Group": | ||||
| 		for member in frappe.db.get_list("Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]): | ||||
| 			recipient_list.append(member['email']) | ||||
| 	else: | ||||
| 		recipient_list.append(frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id")) | ||||
| 
 | ||||
| 	email_template = frappe.get_doc("Email Template", entry.get("email_template")) | ||||
| 	sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email') | ||||
| 	sender = frappe.db.get_value("User", email_campaign.get("sender"), "email") | ||||
| 	context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)} | ||||
| 	# send mail and link communication to document | ||||
| 	comm = make( | ||||
| @ -82,7 +87,7 @@ def send_mail(entry, email_campaign): | ||||
| 		subject = frappe.render_template(email_template.get("subject"), context), | ||||
| 		content = frappe.render_template(email_template.get("response"), context), | ||||
| 		sender = sender, | ||||
| 		recipients = recipient, | ||||
| 		recipients = recipient_list, | ||||
| 		communication_medium = "Email", | ||||
| 		sent_or_received = "Sent", | ||||
| 		send_email = True, | ||||
|  | ||||
| @ -152,7 +152,7 @@ def get_fee_components(fee_structure): | ||||
| 	:param fee_structure: Fee Structure. | ||||
| 	""" | ||||
| 	if fee_structure: | ||||
| 		fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx") | ||||
| 		fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") | ||||
| 		return fs | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -168,6 +168,7 @@ | ||||
|    "fieldname": "grand_total_in_words", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "In Words", | ||||
|    "length": 240, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
| @ -272,7 +273,7 @@ | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-15 08:39:20.682837", | ||||
|  "modified": "2020-07-18 05:11:49.905457", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Education", | ||||
|  "name": "Fee Schedule", | ||||
|  | ||||
| @ -162,6 +162,7 @@ frappe.ui.form.on("Fees", { | ||||
| 						$.each(r.message, function(i, d) { | ||||
| 							var row = frappe.model.add_child(frm.doc, "Fee Component", "components"); | ||||
| 							row.fees_category = d.fees_category; | ||||
| 							row.description = d.description; | ||||
| 							row.amount = d.amount; | ||||
| 						}); | ||||
| 					} | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_import": 1, | ||||
|  "autoname": "naming_series:", | ||||
|  "creation": "2015-09-22 16:57:22.143710", | ||||
| @ -253,6 +254,7 @@ | ||||
|    "fieldname": "grand_total_in_words", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "In Words", | ||||
|    "length": 240, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
| @ -336,7 +338,8 @@ | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "modified": "2019-05-25 22:58:20.026368", | ||||
|  "links": [], | ||||
|  "modified": "2020-07-18 05:00:00.621010", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Education", | ||||
|  "name": "Fees", | ||||
|  | ||||
| @ -140,7 +140,7 @@ education.StudentsEditor = Class.extend({ | ||||
| 							frappe.call({ | ||||
| 								method: "erpnext.education.api.mark_attendance", | ||||
| 								freeze: true, | ||||
| 								freeze_message: "Marking attendance", | ||||
| 								freeze_message: __("Marking attendance"), | ||||
| 								args: { | ||||
| 									"students_present": students_present, | ||||
| 									"students_absent": students_absent, | ||||
| @ -180,4 +180,4 @@ education.StudentsEditor = Class.extend({ | ||||
| 			</div>` | ||||
| 		); | ||||
| 	} | ||||
| }); | ||||
| }); | ||||
|  | ||||
| @ -42,7 +42,7 @@ def execute(filters=None): | ||||
| 				# create the list of possible grades | ||||
| 				if student_row[scrub_criteria] not in grades: | ||||
| 					grades.append(student_row[scrub_criteria]) | ||||
| 				 | ||||
| 
 | ||||
| 				# create the dict of for gradewise analysis | ||||
| 				if student_row[scrub_criteria] not in grade_wise_analysis[criteria]: | ||||
| 					grade_wise_analysis[criteria][student_row[scrub_criteria]] = 1 | ||||
| @ -101,7 +101,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, | ||||
| 
 | ||||
| 	# create the nested dictionary structure as given below: | ||||
| 	# <variable_name>.<student_name>.<course>.<assessment_group>.<assessment_criteria>.<grade/score/max_score> | ||||
| 	# "Total Score" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments | ||||
| 	# "Final Grade" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments | ||||
| 
 | ||||
| 	student_details = {} | ||||
| 	formatted_assessment_result = defaultdict(dict) | ||||
| @ -123,13 +123,13 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, | ||||
| 		formatted_assessment_result[result.student][result.course][assessment_group]\ | ||||
| 			[assessment_criteria]["grade"] = tmp_grade | ||||
| 
 | ||||
| 	# create the assessment criteria "Total Score" with the sum of all the scores of the assessment criteria in a given assessment group | ||||
| 	# create the assessment criteria "Final Grade" with the sum of all the scores of the assessment criteria in a given assessment group | ||||
| 	def add_total_score(result, assessment_group): | ||||
| 		if "Total Score" not in formatted_assessment_result[result.student][result.course][assessment_group]: | ||||
| 			formatted_assessment_result[result.student][result.course][assessment_group]["Total Score"] = frappe._dict({ | ||||
| 				"assessment_criteria": "Total Score", "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade}) | ||||
| 		if "Final Grade" not in formatted_assessment_result[result.student][result.course][assessment_group]: | ||||
| 			formatted_assessment_result[result.student][result.course][assessment_group]["Final Grade"] = frappe._dict({ | ||||
| 				"assessment_criteria": "Final Grade", "maximum_score": result.maximum_score, "score": result.score, "grade": result.grade}) | ||||
| 		else: | ||||
| 			add_score_and_recalculate_grade(result, assessment_group, "Total Score") | ||||
| 			add_score_and_recalculate_grade(result, assessment_group, "Final Grade") | ||||
| 
 | ||||
| 	for result in assessment_result: | ||||
| 		if result.student not in student_details: | ||||
| @ -152,7 +152,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, | ||||
| 		elif create_total_dict: | ||||
| 			if get_all_assessment_groups: | ||||
| 				formatted_assessment_result[result.student][result.course][result.assessment_group]\ | ||||
| 					[result.assessment_criteria] = assessment_criteria_details				 | ||||
| 					[result.assessment_criteria] = assessment_criteria_details | ||||
| 			if not formatted_assessment_result[result.student][result.course][args.assessment_group]: | ||||
| 				formatted_assessment_result[result.student][result.course][args.assessment_group] = defaultdict(dict) | ||||
| 				formatted_assessment_result[result.student][result.course][args.assessment_group]\ | ||||
| @ -166,7 +166,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, | ||||
| 			add_total_score(result, args.assessment_group) | ||||
| 
 | ||||
| 		total_maximum_score = formatted_assessment_result[result.student][result.course][args.assessment_group]\ | ||||
| 			["Total Score"]["maximum_score"] | ||||
| 			["Final Grade"]["maximum_score"] | ||||
| 		if get_assessment_criteria: | ||||
| 			assessment_criteria_dict[result.assessment_criteria] = formatted_assessment_result[result.student][result.course]\ | ||||
| 				[args.assessment_group][result.assessment_criteria]["maximum_score"] | ||||
| @ -174,7 +174,7 @@ def get_formatted_result(args, get_assessment_criteria=False, get_course=False, | ||||
| 			course_dict[result.course] = total_maximum_score | ||||
| 
 | ||||
| 	if get_assessment_criteria and total_maximum_score: | ||||
| 		assessment_criteria_dict["Total Score"] = total_maximum_score | ||||
| 		assessment_criteria_dict["Final Grade"] = total_maximum_score | ||||
| 
 | ||||
| 	return { | ||||
| 		"student_details": student_details, | ||||
| @ -220,7 +220,7 @@ def get_chart_data(grades, criteria_list, kounter): | ||||
| 	datasets = [] | ||||
| 
 | ||||
| 	for grade in grades: | ||||
| 		tmp = frappe._dict({"values":[], "title": grade}) | ||||
| 		tmp = frappe._dict({"name": grade, "values":[]}) | ||||
| 		for criteria in criteria_list: | ||||
| 			if grade in kounter[criteria]: | ||||
| 				tmp["values"].append(kounter[criteria][grade]) | ||||
|  | ||||
| @ -38,7 +38,7 @@ | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "label": "Records and History", | ||||
|    "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]" | ||||
|    "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
| @ -64,7 +64,7 @@ | ||||
|  "idx": 0, | ||||
|  "is_standard": 1, | ||||
|  "label": "Healthcare", | ||||
|  "modified": "2020-05-28 19:02:28.824995", | ||||
|  "modified": "2020-06-25 23:50:56.951698", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Healthcare", | ||||
|  "name": "Healthcare", | ||||
|  | ||||
| @ -39,7 +39,9 @@ class HealthcareServiceUnitType(Document): | ||||
| 	def on_trash(self): | ||||
| 		if self.item: | ||||
| 			try: | ||||
| 				frappe.delete_doc('Item', self.item) | ||||
| 				item = self.item | ||||
| 				self.db_set('item', '') | ||||
| 				frappe.delete_doc('Item', item) | ||||
| 			except Exception: | ||||
| 				frappe.throw(_('Not permitted. Please disable the Service Unit Type')) | ||||
| 
 | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user