mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-25 04:53:01 +00:00
Merge branch 'develop' into care
This commit is contained in:
@@ -685,13 +685,17 @@ class AccountsController(TransactionBase):
|
||||
.format(d.reference_name, d.against_order))
|
||||
|
||||
def set_advance_gain_or_loss(self):
|
||||
if not self.get("advances"):
|
||||
if self.get('conversion_rate') == 1 or not self.get("advances"):
|
||||
return
|
||||
|
||||
is_purchase_invoice = self.doctype == 'Purchase Invoice'
|
||||
party_account = self.credit_to if is_purchase_invoice else self.debit_to
|
||||
if get_account_currency(party_account) != self.currency:
|
||||
return
|
||||
|
||||
for d in self.get("advances"):
|
||||
advance_exchange_rate = d.ref_exchange_rate
|
||||
if (d.allocated_amount and self.conversion_rate != 1
|
||||
and self.conversion_rate != advance_exchange_rate):
|
||||
if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
|
||||
|
||||
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
|
||||
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
|
||||
@@ -710,7 +714,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||
if not gain_loss_account:
|
||||
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
|
||||
frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}")
|
||||
.format(self.get('company')))
|
||||
account_currency = get_account_currency(gain_loss_account)
|
||||
if account_currency != self.company_currency:
|
||||
@@ -729,7 +733,7 @@ class AccountsController(TransactionBase):
|
||||
"against": party,
|
||||
dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
|
||||
dr_or_cr: abs(d.exchange_gain_loss),
|
||||
"cost_center": self.cost_center,
|
||||
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
|
||||
"project": self.project
|
||||
}, item=d)
|
||||
)
|
||||
@@ -980,42 +984,55 @@ class AccountsController(TransactionBase):
|
||||
item_allowance = {}
|
||||
global_qty_allowance, global_amount_allowance = None, None
|
||||
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
user_roles = frappe.get_roles()
|
||||
|
||||
total_overbilled_amt = 0.0
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get(item_ref_dn):
|
||||
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
|
||||
item.get(item_ref_dn), based_on), self.precision(based_on, item))
|
||||
if not ref_amt:
|
||||
frappe.msgprint(
|
||||
_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
|
||||
.format(item.item_code, ref_dt))
|
||||
else:
|
||||
already_billed = frappe.db.sql("""
|
||||
select sum(%s)
|
||||
from `tab%s`
|
||||
where %s=%s and docstatus=1 and parent != %s
|
||||
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
|
||||
(item.get(item_ref_dn), self.name))[0][0]
|
||||
if not item.get(item_ref_dn):
|
||||
continue
|
||||
|
||||
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
|
||||
self.precision(based_on, item))
|
||||
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
|
||||
item.get(item_ref_dn), based_on), self.precision(based_on, item))
|
||||
if not ref_amt:
|
||||
frappe.msgprint(
|
||||
_("System will not check overbilling since amount for Item {0} in {1} is zero")
|
||||
.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
|
||||
continue
|
||||
|
||||
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
|
||||
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
|
||||
already_billed = frappe.db.sql("""
|
||||
select sum(%s)
|
||||
from `tab%s`
|
||||
where %s=%s and docstatus=1 and parent != %s
|
||||
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
|
||||
(item.get(item_ref_dn), self.name))[0][0]
|
||||
|
||||
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
|
||||
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
|
||||
self.precision(based_on, item))
|
||||
|
||||
if total_billed_amt < 0 and max_allowed_amt < 0:
|
||||
# while making debit note against purchase return entry(purchase receipt) getting overbill error
|
||||
total_billed_amt = abs(total_billed_amt)
|
||||
max_allowed_amt = abs(max_allowed_amt)
|
||||
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
|
||||
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
|
||||
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
|
||||
|
||||
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
|
||||
if self.doctype != "Purchase Invoice":
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
if total_billed_amt < 0 and max_allowed_amt < 0:
|
||||
# while making debit note against purchase return entry(purchase receipt) getting overbill error
|
||||
total_billed_amt = abs(total_billed_amt)
|
||||
max_allowed_amt = abs(max_allowed_amt)
|
||||
|
||||
overbill_amt = total_billed_amt - max_allowed_amt
|
||||
total_overbilled_amt += overbill_amt
|
||||
|
||||
if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
|
||||
if self.doctype != "Purchase Invoice":
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
|
||||
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
|
||||
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
|
||||
.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
|
||||
|
||||
def throw_overbill_exception(self, item, max_allowed_amt):
|
||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||
@@ -1668,14 +1685,18 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
||||
return list(payment_entries_against_order) + list(unallocated_payment_entries)
|
||||
|
||||
def update_invoice_status():
|
||||
# Daily update the status of the invoices
|
||||
|
||||
frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue'
|
||||
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
|
||||
|
||||
frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue'
|
||||
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
|
||||
"""Updates status as Overdue for applicable invoices. Runs daily."""
|
||||
|
||||
for doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
frappe.db.sql("""
|
||||
update `tab{}` as dt set dt.status = 'Overdue'
|
||||
where dt.docstatus = 1
|
||||
and dt.status != 'Overdue'
|
||||
and dt.outstanding_amount > 0
|
||||
and (dt.grand_total - dt.outstanding_amount) <
|
||||
(select sum(payment_amount) from `tabPayment Schedule` as ps
|
||||
where ps.parent = dt.name and ps.due_date < %s)
|
||||
""".format(doctype), getdate())
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import scrub
|
||||
from frappe.desk.reportview import get_filters_cond, get_match_cond
|
||||
from frappe.utils import nowdate, unique
|
||||
|
||||
@@ -223,18 +224,29 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
if not field in searchfields]
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
if filters and isinstance(filters, dict) and filters.get('supplier'):
|
||||
item_group_list = frappe.get_all('Supplier Item Group',
|
||||
filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
|
||||
if filters and isinstance(filters, dict):
|
||||
if filters.get('customer') or filters.get('supplier'):
|
||||
party = filters.get('customer') or filters.get('supplier')
|
||||
item_rules_list = frappe.get_all('Party Specific Item',
|
||||
filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
|
||||
|
||||
item_groups = []
|
||||
for i in item_group_list:
|
||||
item_groups.append(i.item_group)
|
||||
filters_dict = {}
|
||||
for rule in item_rules_list:
|
||||
if rule['restrict_based_on'] == 'Item':
|
||||
rule['restrict_based_on'] = 'name'
|
||||
filters_dict[rule.restrict_based_on] = []
|
||||
|
||||
del filters['supplier']
|
||||
for rule in item_rules_list:
|
||||
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
|
||||
|
||||
for filter in filters_dict:
|
||||
filters[scrub(filter)] = ['in', filters_dict[filter]]
|
||||
|
||||
if filters.get('customer'):
|
||||
del filters['customer']
|
||||
else:
|
||||
del filters['supplier']
|
||||
|
||||
if item_groups:
|
||||
filters['item_group'] = ['in', item_groups]
|
||||
|
||||
description_cond = ''
|
||||
if frappe.db.count('Item', cache=True) < 50000:
|
||||
@@ -307,7 +319,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ''
|
||||
if filters.get('customer'):
|
||||
if filters and filters.get('customer'):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
|
||||
|
||||
|
||||
87
erpnext/controllers/tests/test_queries.py
Normal file
87
erpnext/controllers/tests/test_queries.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import unittest
|
||||
from functools import partial
|
||||
|
||||
from erpnext.controllers import queries
|
||||
|
||||
|
||||
def add_default_params(func, doctype):
|
||||
return partial(
|
||||
func, doctype=doctype, txt="", searchfield="name", start=0, page_len=20, filters=None
|
||||
)
|
||||
|
||||
|
||||
class TestQueries(unittest.TestCase):
|
||||
|
||||
# All tests are based on doctype/test_records.json
|
||||
|
||||
def assert_nested_in(self, item, container):
|
||||
self.assertIn(item, [vals for tuples in container for vals in tuples])
|
||||
|
||||
def test_employee_query(self):
|
||||
query = add_default_params(queries.employee_query, "Employee")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Employee")), 3)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Employee 1")), 1)
|
||||
|
||||
def test_lead_query(self):
|
||||
query = add_default_params(queries.lead_query, "Lead")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
|
||||
self.assertEqual(len(query(txt="_Test Lead 4")), 1)
|
||||
|
||||
def test_customer_query(self):
|
||||
query = add_default_params(queries.customer_query, "Customer")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Customer")), 7)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1)
|
||||
|
||||
def test_supplier_query(self):
|
||||
query = add_default_params(queries.supplier_query, "Supplier")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1)
|
||||
|
||||
def test_item_query(self):
|
||||
query = add_default_params(queries.item_query, "Item")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Item")), 7)
|
||||
self.assertEqual(len(query(txt="_Test Item Home Desktop 100 3")), 1)
|
||||
|
||||
fg_item = "_Test FG Item"
|
||||
stock_items = query(txt=fg_item, filters={"is_stock_item": 1})
|
||||
self.assert_nested_in("_Test FG Item", stock_items)
|
||||
|
||||
bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1})
|
||||
self.assertEqual(len(bundled_stock_items), 0)
|
||||
|
||||
def test_bom_qury(self):
|
||||
query = add_default_params(queries.bom, "BOM")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1)
|
||||
|
||||
def test_project_query(self):
|
||||
query = add_default_params(queries.get_project_name, "BOM")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Project")), 1)
|
||||
|
||||
def test_account_query(self):
|
||||
query = add_default_params(queries.get_account_list, "Account")
|
||||
|
||||
debtor_accounts = query(txt="Debtors", filters={"company": "_Test Company"})
|
||||
self.assert_nested_in("Debtors - _TC", debtor_accounts)
|
||||
|
||||
def test_income_account_query(self):
|
||||
query = add_default_params(queries.get_income_account, "Account")
|
||||
|
||||
self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1)
|
||||
|
||||
def test_expense_account_query(self):
|
||||
query = add_default_params(queries.get_expense_account, "Account")
|
||||
|
||||
self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1)
|
||||
|
||||
def test_warehouse_query(self):
|
||||
query = add_default_params(queries.warehouse_query, "Account")
|
||||
|
||||
wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]])
|
||||
self.assertGreaterEqual(len(wh), 1)
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.modules.utils import get_module_app
|
||||
from frappe.utils import flt, has_common
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
@@ -21,8 +22,32 @@ def get_list_context(context=None):
|
||||
"get_list": get_transaction_list
|
||||
}
|
||||
|
||||
def get_webform_list_context(module):
|
||||
if get_module_app(module) != 'erpnext':
|
||||
return
|
||||
return {
|
||||
"get_list": get_webform_transaction_list
|
||||
}
|
||||
|
||||
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
|
||||
def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
|
||||
""" Get List of transactions for custom doctypes """
|
||||
from frappe.www.list import get_list
|
||||
|
||||
if not filters:
|
||||
filters = []
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
for d in meta.fields:
|
||||
if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
|
||||
allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
|
||||
allowed_docs.append('')
|
||||
filters.append((d.fieldname, 'in', allowed_docs))
|
||||
|
||||
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
|
||||
fields=None, order_by="modified")
|
||||
|
||||
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
|
||||
user = frappe.session.user
|
||||
ignore_permissions = False
|
||||
|
||||
@@ -46,7 +71,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
||||
filters.append(('customer', 'in', customers))
|
||||
elif suppliers:
|
||||
filters.append(('supplier', 'in', suppliers))
|
||||
else:
|
||||
elif not custom:
|
||||
return []
|
||||
|
||||
if doctype == 'Request for Quotation':
|
||||
@@ -56,9 +81,16 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
||||
# Since customers and supplier do not have direct access to internal doctypes
|
||||
ignore_permissions = True
|
||||
|
||||
if not customers and not suppliers and custom:
|
||||
ignore_permissions = False
|
||||
filters = []
|
||||
|
||||
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
|
||||
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
|
||||
|
||||
if custom:
|
||||
return transactions
|
||||
|
||||
return post_process(doctype, transactions)
|
||||
|
||||
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
|
||||
|
||||
Reference in New Issue
Block a user