Merge branch 'develop' of https://github.com/frappe/erpnext into rebrand-ui

This commit is contained in:
Shivam Mishra 2020-07-30 21:29:02 +05:30
commit 11111cd9a9
180 changed files with 11581 additions and 9014 deletions

View File

@ -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"
}
]
}
}

View File

@ -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):

View File

@ -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):

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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);
}
}
]);

View File

@ -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:

View File

@ -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")

View File

@ -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>

View 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);
}
})
}

View File

@ -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
}

View 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

View File

@ -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'}
]),

View File

@ -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

View File

@ -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
}

View File

@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
class POSClosingVoucherTaxes(Document):
class POSClosingEntryDetail(Document):
pass

View File

@ -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
}

View File

@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
class POSClosingVoucherDetails(Document):
class POSClosingEntryTaxes(Document):
pass

View 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);
}
}
});

File diff suppressed because it is too large Load Diff

View 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()

View 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);
}
}
});
});
},
};

View 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

View 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"
}

View File

@ -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

View File

@ -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': ''
}
}
});
}
});

View File

@ -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
}

View File

@ -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()

View File

@ -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`")

View File

@ -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
}

View File

@ -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

View File

@ -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");
}
});
}
}
});

View File

@ -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
}

View File

@ -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)

View File

@ -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];
}
};

View File

@ -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()

View File

@ -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
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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'));

View File

@ -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",

View File

@ -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):

View File

@ -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']
}
]
}

View File

@ -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()

View File

@ -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",

View File

@ -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);
});
}
});

View File

@ -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",

View File

@ -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

View File

@ -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);
}
}

View File

@ -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",

View File

@ -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

View File

@ -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`

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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()
]);
});

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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`

View 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"
}

View File

@ -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": []
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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",

View 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"
}

View File

@ -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"
}

View 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"
}

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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"],
]
}

View File

@ -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:

View File

@ -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",

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -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;
});
}

View File

@ -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",

View File

@ -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>`
);
}
});
});

View File

@ -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])

View File

@ -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",

View File

@ -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