Merge branch 'staging-fixes' of https://github.com/frappe/erpnext into loyalty-program-fix

This commit is contained in:
deepeshgarg007
2019-01-01 19:46:08 +05:30
30 changed files with 686 additions and 316 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '10.1.74' __version__ = '10.1.76'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -830,6 +830,10 @@ class PurchaseInvoice(BuyingController):
return return
tax_withholding_details = get_party_tax_withholding_details(self) tax_withholding_details = get_party_tax_withholding_details(self)
if not tax_withholding_details:
return
accounts = [] accounts = []
for d in self.taxes: for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"): if d.account_head == tax_withholding_details.get("account_head"):
@@ -839,6 +843,12 @@ class PurchaseInvoice(BuyingController):
if not accounts or tax_withholding_details.get("account_head") not in accounts: if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("taxes", tax_withholding_details) self.append("taxes", tax_withholding_details)
to_remove = [d for d in self.taxes
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
for d in to_remove:
self.remove(d)
# calculate totals again after applying TDS # calculate totals again after applying TDS
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()

View File

@@ -24,6 +24,7 @@ def get_party_tax_withholding_details(ref_doc):
.format(tax_withholding_category, ref_doc.company)) .format(tax_withholding_category, ref_doc.company))
tds_amount = get_tds_amount(ref_doc, tax_details, fy) tds_amount = get_tds_amount(ref_doc, tax_details, fy)
tax_row = get_tax_row(tax_details, tds_amount) tax_row = get_tax_row(tax_details, tds_amount)
return tax_row return tax_row
def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
@@ -62,46 +63,64 @@ def get_tax_row(tax_details, tds_amount):
def get_tds_amount(ref_doc, tax_details, fiscal_year_details): def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
fiscal_year, year_start_date, year_end_date = fiscal_year_details fiscal_year, year_start_date, year_end_date = fiscal_year_details
tds_amount = 0 tds_amount = 0
tds_deducted = 0
def _get_tds(): def _get_tds(amount):
tds_amount = 0 if amount <= 0:
if not tax_details.threshold or ref_doc.net_total >= tax_details.threshold: return 0
tds_amount = ref_doc.net_total * tax_details.rate / 100
return tds_amount
if tax_details.cumulative_threshold: return amount * tax_details.rate / 100
entries = frappe.db.sql("""
entries = frappe.db.sql("""
select voucher_no, credit select voucher_no, credit
from `tabGL Entry` from `tabGL Entry`
where party=%s and fiscal_year=%s and credit > 0 where party=%s and fiscal_year=%s and credit > 0
""", (ref_doc.supplier, fiscal_year), as_dict=1) """, (ref_doc.supplier, fiscal_year), as_dict=1)
supplier_credit_amount = flt(sum([d.credit for d in entries])) vouchers = [d.voucher_no for d in entries]
advance_vouchers = get_advance_vouchers(ref_doc.supplier, fiscal_year)
vouchers = [d.voucher_no for d in entries] tds_vouchers = vouchers + advance_vouchers
vouchers += get_advance_vouchers(ref_doc.supplier, fiscal_year)
tds_deducted = 0 if tds_vouchers:
if vouchers: tds_deducted = frappe.db.sql("""
tds_deducted = flt(frappe.db.sql(""" SELECT sum(credit) FROM `tabGL Entry`
select sum(credit) WHERE
from `tabGL Entry` account=%s and fiscal_year=%s and credit > 0
where account=%s and fiscal_year=%s and credit > 0 and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))),
and voucher_no in ({0}) ((tax_details.account_head, fiscal_year) + tuple(tds_vouchers)))
""".format(', '.join(["'%s'" % d for d in vouchers])),
(tax_details.account_head, fiscal_year))[0][0]) tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
if tds_deducted:
tds_amount = _get_tds(ref_doc.net_total)
else:
supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
fields = ['sum(net_amount)'],
filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1)
supplier_credit_amount = (supplier_credit_amount[0][0]
if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
jv_supplier_credit_amt = frappe.get_all('Journal Entry Account',
fields = ['sum(credit_in_account_currency)'],
filters = {
'parent': ('in', vouchers), 'docstatus': 1,
'party': ref_doc.supplier,
'reference_type': ('not in', ['Purchase Invoice'])
}, as_list=1)
supplier_credit_amount += (jv_supplier_credit_amt[0][0]
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
supplier_credit_amount += ref_doc.net_total
debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date) debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date)
supplier_credit_amount -= debit_note_amount
total_invoiced_amount = supplier_credit_amount + tds_deducted \ if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
+ flt(ref_doc.net_total) - debit_note_amount or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
if total_invoiced_amount >= tax_details.cumulative_threshold: tds_amount = _get_tds(supplier_credit_amount)
total_applicable_tds = total_invoiced_amount * tax_details.rate / 100
tds_amount = min(total_applicable_tds - tds_deducted, ref_doc.net_total)
else:
tds_amount = _get_tds()
else:
tds_amount = _get_tds()
return tds_amount return tds_amount
@@ -114,7 +133,7 @@ def get_advance_vouchers(supplier, fiscal_year=None, company=None, from_date=Non
select distinct voucher_no select distinct voucher_no
from `tabGL Entry` from `tabGL Entry`
where party=%s and %s and debit > 0 where party=%s and %s and debit > 0
""", (supplier, condition)) """, (supplier, condition)) or []
def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None): def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None):
condition = "" condition = ""
@@ -126,4 +145,4 @@ def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None
from `tabPurchase Invoice` from `tabPurchase Invoice`
where supplier=%s %s and is_return=1 and docstatus=1 where supplier=%s %s and is_return=1 and docstatus=1
and posting_date between %s and %s and posting_date between %s and %s
""", (supplier, condition, year_start_date, year_end_date))) """, (supplier, condition, year_start_date, year_end_date)))

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from frappe.utils import today from frappe.utils import today
from erpnext.accounts.utils import get_fiscal_year
test_dependencies = ["Supplier Group"] test_dependencies = ["Supplier Group"]
@@ -14,65 +15,105 @@ class TestTaxWithholdingCategory(unittest.TestCase):
def setUpClass(self): def setUpClass(self):
# create relevant supplier, etc # create relevant supplier, etc
create_records() create_records()
create_tax_with_holding_category()
def test_single_threshold_tds(self): def test_cumulative_threshold_tds(self):
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194D - Individual") frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS")
pi = create_purchase_invoice() invoices = []
# create invoices for lower than single threshold tax rate
for _ in xrange(2):
pi = create_purchase_invoice(supplier = "Test TDS Supplier")
pi.submit()
invoices.append(pi)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
pi = create_purchase_invoice(supplier = "Test TDS Supplier")
pi.submit() pi.submit()
self.assertEqual(pi.taxes_and_charges_deducted, 800) # assert equal tax deduction on total invoice amount uptil now
self.assertEqual(pi.grand_total, 15200) self.assertEqual(pi.taxes_and_charges_deducted, 3000)
self.assertEqual(pi.grand_total, 7000)
invoices.append(pi)
# TDS is already deducted, so from onward system will deduct the TDS on every invoice
pi = create_purchase_invoice(supplier = "Test TDS Supplier", rate=5000)
pi.submit()
# assert equal tax deduction on total invoice amount uptil now
self.assertEqual(pi.taxes_and_charges_deducted, 500)
invoices.append(pi)
#delete invoices to avoid clashing
for d in invoices:
d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds(self):
invoices = []
frappe.db.set_value("Supplier", "Test TDS Supplier1", "tax_withholding_category", "Single Threshold TDS")
pi = create_purchase_invoice(supplier = "Test TDS Supplier1", rate = 20000)
pi.submit()
invoices.append(pi)
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
self.assertEqual(pi.grand_total, 18000)
# check gl entry for the purchase invoice # check gl entry for the purchase invoice
gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"]) gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"])
self.assertEqual(len(gl_entries), 3) self.assertEqual(len(gl_entries), 3)
for d in gl_entries: for d in gl_entries:
if d.account == pi.credit_to: if d.account == pi.credit_to:
self.assertEqual(d.credit, 15200) self.assertEqual(d.credit, 18000)
elif d.account == pi.items[0].get("expense_account"): elif d.account == pi.items[0].get("expense_account"):
self.assertEqual(d.debit, 16000) self.assertEqual(d.debit, 20000)
elif d.account == pi.taxes[0].get("account_head"): elif d.account == pi.taxes[0].get("account_head"):
self.assertEqual(d.credit, 800) self.assertEqual(d.credit, 2000)
else: else:
raise ValueError("Account head does not match.") raise ValueError("Account head does not match.")
# delete purchase invoice to avoid it interefering in other tests pi = create_purchase_invoice(supplier = "Test TDS Supplier1")
pi.cancel()
frappe.delete_doc('Purchase Invoice', pi.name)
def test_cumulative_threshold_tds(self):
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194C - Individual")
invoices = []
# create invoices for lower than single threshold tax rate
for _ in xrange(6):
pi = create_purchase_invoice()
pi.submit()
invoices.append(pi)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
pi = create_purchase_invoice()
pi.submit() pi.submit()
# assert equal tax deduction on total invoice amount uptil now
self.assertEqual(pi.taxes_and_charges_deducted, 1120)
self.assertEqual(pi.grand_total, 14880)
invoices.append(pi) invoices.append(pi)
# TDS amount is 1000 because in previous invoices it's already deducted
self.assertEqual(pi.taxes_and_charges_deducted, 1000)
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name) frappe.delete_doc("Purchase Invoice", d.name)
def create_purchase_invoice(qty=1): def test_single_threshold_tds_with_previous_vouchers(self):
invoices = []
frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS")
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
pi.submit()
invoices.append(pi)
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
pi.submit()
invoices.append(pi)
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
self.assertEqual(pi.grand_total, 8000)
# delete invoices to avoid clashing
for d in invoices:
d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def create_purchase_invoice(**args):
# return sales invoice doc object # return sales invoice doc object
item = frappe.get_doc('Item', {'item_name': 'TDS Item'}) item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
args = frappe._dict(args)
pi = frappe.get_doc({ pi = frappe.get_doc({
"doctype": "Purchase Invoice", "doctype": "Purchase Invoice",
"posting_date": today(), "posting_date": today(),
"apply_tds": 1, "apply_tds": 1,
"supplier": frappe.get_doc('Supplier', {"supplier_name": "Test TDS Supplier"}).name, "supplier": args.supplier,
"company": '_Test Company', "company": '_Test Company',
"taxes_and_charges": "", "taxes_and_charges": "",
"currency": "INR", "currency": "INR",
@@ -81,8 +122,8 @@ def create_purchase_invoice(qty=1):
"items": [{ "items": [{
'doctype': 'Purchase Invoice Item', 'doctype': 'Purchase Invoice Item',
'item_code': item.name, 'item_code': item.name,
'qty': qty, 'qty': args.qty or 1,
'rate': 16000, 'rate': args.rate or 10000,
'cost_center': 'Main - _TC', 'cost_center': 'Main - _TC',
'expense_account': 'Stock Received But Not Billed - _TC' 'expense_account': 'Stock Received But Not Billed - _TC'
}] }]
@@ -92,20 +133,73 @@ def create_purchase_invoice(qty=1):
return pi return pi
def create_records(): def create_records():
# create a new supplier # create a new suppliers
frappe.get_doc({ for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
"supplier_group": "_Test Supplier Group", if frappe.db.exists('Supplier', name):
"supplier_name": "Test TDS Supplier", continue
"doctype": "Supplier",
"tax_withholding_category": "TDS - 194D - Individual" frappe.get_doc({
}).insert() "supplier_group": "_Test Supplier Group",
"supplier_name": name,
"doctype": "Supplier",
}).insert()
# create an item # create an item
frappe.get_doc({ if not frappe.db.exists('Item', "TDS Item"):
"doctype": "Item", frappe.get_doc({
"item_code": "TDS Item", "doctype": "Item",
"item_name": "TDS Item", "item_code": "TDS Item",
"item_group": "All Item Groups", "item_name": "TDS Item",
"company": "_Test Company", "item_group": "All Item Groups",
"is_stock_item": 0, "is_stock_item": 0,
}).insert() }).insert()
# create an account
if not frappe.db.exists("Account", "TDS - _TC"):
frappe.get_doc({
'doctype': 'Account',
'company': '_Test Company',
'account_name': 'TDS',
'parent_account': 'Tax Assets - _TC',
'report_type': 'Balance Sheet',
'root_type': 'Asset'
}).insert()
def create_tax_with_holding_category():
fiscal_year = get_fiscal_year(today(), company="_Test Company")[0]
# Cummulative thresold
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Cumulative Threshold TDS",
"category_name": "10% TDS",
"rates": [{
'fiscal_year': fiscal_year,
'tax_withholding_rate': 10,
'single_threshold': 0,
'cumulative_threshold': 30000.00
}],
"accounts": [{
'company': '_Test Company',
'account': 'TDS - _TC'
}]
}).insert()
# Single thresold
if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Single Threshold TDS",
"category_name": "10% TDS",
"rates": [{
'fiscal_year': fiscal_year,
'tax_withholding_rate': 10,
'single_threshold': 20000.00,
'cumulative_threshold': 0
}],
"accounts": [{
'company': '_Test Company',
'account': 'TDS - _TC'
}]
}).insert()

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _, msgprint, scrub from frappe import _, msgprint, scrub
from frappe.defaults import get_user_permissions from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
from frappe.utils import (add_days, getdate, formatdate, date_diff, from frappe.utils import (add_days, getdate, formatdate, date_diff,
add_years, get_timestamp, nowdate, flt, add_months, get_last_day) add_years, get_timestamp, nowdate, flt, add_months, get_last_day)
@@ -151,10 +151,7 @@ def get_default_price_list(party):
def set_price_list(out, party, party_type, given_price_list): def set_price_list(out, party, party_type, given_price_list):
# price list # price list
price_list = filter(None, get_user_permissions() price_list = get_permitted_documents('Price List')
.get("Price List", {})
.get("docs", []))
price_list = list(price_list)
if price_list: if price_list:
price_list = price_list[0] price_list = price_list[0]

View File

@@ -108,33 +108,33 @@ def get_conditions(filters):
def get_pos_invoice_data(filters): def get_pos_invoice_data(filters):
conditions = get_conditions(filters) conditions = get_conditions(filters)
result = frappe.db.sql('' result = frappe.db.sql(''
'SELECT ' 'SELECT '
'posting_date, owner, sum(net_total) as "net_total", sum(total_taxes) as "total_taxes", ' 'posting_date, owner, sum(net_total) as "net_total", sum(total_taxes) as "total_taxes", '
'sum(paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount", ' 'sum(paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount", '
'mode_of_payment, warehouse, cost_center ' 'mode_of_payment, warehouse, cost_center '
'FROM (' 'FROM ('
'SELECT ' 'SELECT '
'parent, item_code, sum(amount) as "base_total", warehouse, cost_center ' 'parent, item_code, sum(amount) as "base_total", warehouse, cost_center '
'from `tabSales Invoice Item` group by parent' 'from `tabSales Invoice Item` group by parent'
') t1 ' ') t1 '
'left join ' 'left join '
'(select parent, mode_of_payment from `tabSales Invoice Payment` group by parent) t3 ' '(select parent, mode_of_payment from `tabSales Invoice Payment` group by parent) t3 '
'on (t3.parent = t1.parent) ' 'on (t3.parent = t1.parent) '
'JOIN (' 'JOIN ('
'SELECT ' 'SELECT '
'docstatus, company, is_pos, name, posting_date, owner, sum(base_total) as "base_total", ' 'docstatus, company, is_pos, name, posting_date, owner, sum(base_total) as "base_total", '
'sum(net_total) as "net_total", sum(total_taxes_and_charges) as "total_taxes", ' 'sum(net_total) as "net_total", sum(total_taxes_and_charges) as "total_taxes", '
'sum(base_paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount" ' 'sum(base_paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount" '
'FROM `tabSales Invoice` ' 'FROM `tabSales Invoice` '
'GROUP BY name' 'GROUP BY name'
') a ' ') a '
'ON (' 'ON ('
't1.parent = a.name and t1.base_total = a.base_total) ' 't1.parent = a.name and t1.base_total = a.base_total) '
'WHERE a.docstatus = 1' 'WHERE a.docstatus = 1'
' AND {conditions} ' ' AND {conditions} '
'GROUP BY ' 'GROUP BY '
'owner, posting_date, warehouse'.format(conditions=conditions), filters, as_dict=1 'owner, posting_date, warehouse'.format(conditions=conditions), filters, as_dict=1
) )
return result return result
@@ -156,7 +156,6 @@ def get_sales_invoice_data(filters):
def get_mode_of_payments(filters): def get_mode_of_payments(filters):
frappe.log_error(filters, 'filters')
mode_of_payments = {} mode_of_payments = {}
invoice_list = get_invoices(filters) invoice_list = get_invoices(filters)
invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
@@ -164,12 +163,14 @@ def get_mode_of_payments(filters):
inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
from `tabSales Invoice` a, `tabSales Invoice Payment` b from `tabSales Invoice` a, `tabSales Invoice Payment` b
where a.name = b.parent where a.name = b.parent
and a.docstatus = 1
and a.name in ({invoice_list_names}) and a.name in ({invoice_list_names})
union union
select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
where a.name = c.reference_name where a.name = c.reference_name
and b.name = c.parent and b.name = c.parent
and b.docstatus = 1
and a.name in ({invoice_list_names}) and a.name in ({invoice_list_names})
union union
select a.owner, a.posting_date, select a.owner, a.posting_date,
@@ -196,13 +197,13 @@ def get_invoices(filters):
def get_mode_of_payment_details(filters): def get_mode_of_payment_details(filters):
mode_of_payment_details = {} mode_of_payment_details = {}
invoice_list = get_invoices(filters) invoice_list = get_invoices(filters)
frappe.log_error(invoice_list, 'invoice_list')
invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
if invoice_list: if invoice_list:
inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date, inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
from `tabSales Invoice` a, `tabSales Invoice Payment` b from `tabSales Invoice` a, `tabSales Invoice Payment` b
where a.name = b.parent where a.name = b.parent
and a.docstatus = 1
and a.name in ({invoice_list_names}) and a.name in ({invoice_list_names})
group by a.owner, a.posting_date, mode_of_payment group by a.owner, a.posting_date, mode_of_payment
union union
@@ -211,6 +212,7 @@ def get_mode_of_payment_details(filters):
from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
where a.name = c.reference_name where a.name = c.reference_name
and b.name = c.parent and b.name = c.parent
and b.docstatus = 1
and a.name in ({invoice_list_names}) and a.name in ({invoice_list_names})
group by a.owner, a.posting_date, mode_of_payment group by a.owner, a.posting_date, mode_of_payment
union union

View File

@@ -0,0 +1,165 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.accounts.report.sales_payment_summary.sales_payment_summary import get_mode_of_payments, get_mode_of_payment_details
from frappe.utils import today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
test_dependencies = ["Sales Invoice"]
class TestSalesPaymentSummary(unittest.TestCase):
@classmethod
def setUpClass(self):
create_records()
pes = frappe.get_all("Payment Entry")
jes = frappe.get_all("Journal Entry")
sis = frappe.get_all("Sales Invoice")
for pe in pes:
frappe.db.set_value("Payment Entry", pe.name, "docstatus", 2)
for je in jes:
frappe.db.set_value("Journal Entry", je.name, "docstatus", 2)
for si in sis:
frappe.db.set_value("Sales Invoice", si.name, "docstatus", 2)
def test_get_mode_of_payments(self):
filters = get_filters()
for dummy in range(2):
si = create_sales_invoice_record()
si.insert()
si.submit()
if int(si.name[-3:])%2 == 0:
bank_account = "_Test Cash - _TC"
mode_of_payment = "Cash"
else:
bank_account = "_Test Bank - _TC"
mode_of_payment = "Credit Card"
pe = get_payment_entry("Sales Invoice", si.name, bank_account=bank_account)
pe.reference_no = "_Test"
pe.reference_date = today()
pe.mode_of_payment = mode_of_payment
pe.insert()
pe.submit()
mop = get_mode_of_payments(filters)
self.assertTrue('Credit Card' in mop.values()[0])
self.assertTrue('Cash' in mop.values()[0])
# Cancel all Cash payment entry and check if this mode of payment is still fetched.
payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Cash", "docstatus": 1}, fields=["name", "docstatus"])
for payment_entry in payment_entries:
pe = frappe.get_doc("Payment Entry", payment_entry.name)
pe.cancel()
mop = get_mode_of_payments(filters)
self.assertTrue('Credit Card' in mop.values()[0])
self.assertTrue('Cash' not in mop.values()[0])
def test_get_mode_of_payments_details(self):
filters = get_filters()
for dummy in range(2):
si = create_sales_invoice_record()
si.insert()
si.submit()
if int(si.name[-3:])%2 == 0:
bank_account = "_Test Cash - _TC"
mode_of_payment = "Cash"
else:
bank_account = "_Test Bank - _TC"
mode_of_payment = "Credit Card"
pe = get_payment_entry("Sales Invoice", si.name, bank_account=bank_account)
pe.reference_no = "_Test"
pe.reference_date = today()
pe.mode_of_payment = mode_of_payment
pe.insert()
pe.submit()
mopd = get_mode_of_payment_details(filters)
mopd_values = mopd.values()[0]
for mopd_value in mopd_values:
if mopd_value[0] == "Credit Card":
cc_init_amount = mopd_value[1]
# Cancel one Credit Card Payment Entry and check that it is not fetched in mode of payment details.
payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Credit Card", "docstatus": 1}, fields=["name", "docstatus"])
for payment_entry in payment_entries[:1]:
pe = frappe.get_doc("Payment Entry", payment_entry.name)
pe.cancel()
mopd = get_mode_of_payment_details(filters)
mopd_values = mopd.values()[0]
for mopd_value in mopd_values:
if mopd_value[0] == "Credit Card":
cc_final_amount = mopd_value[1]
self.assertTrue(cc_init_amount > cc_final_amount)
def get_filters():
return {
"from_date": "1900-01-01",
"to_date": today(),
"company": "_Test Company"
}
def create_sales_invoice_record(qty=1):
# return sales invoice doc object
return frappe.get_doc({
"doctype": "Sales Invoice",
"customer": frappe.get_doc('Customer', {"customer_name": "Prestiga-Biz"}).name,
"company": '_Test Company',
"due_date": today(),
"posting_date": today(),
"currency": "INR",
"taxes_and_charges": "",
"debit_to": "Debtors - _TC",
"taxes": [],
"items": [{
'doctype': 'Sales Invoice Item',
'item_code': frappe.get_doc('Item', {'item_name': 'Consulting'}).name,
'qty': qty,
"rate": 10000,
'income_account': 'Sales - _TC',
'cost_center': 'Main - _TC',
'expense_account': 'Cost of Goods Sold - _TC'
}]
})
def create_records():
if frappe.db.exists("Customer", "Prestiga-Biz"):
return
#customer
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Prestiga-Biz",
"customer_type": "Company",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
# item
item = frappe.get_doc({
"doctype": "Item",
"item_code": "Consulting",
"item_name": "Consulting",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 0
}).insert()
# item price
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": item.item_code,
"price_list_rate": 10000
}).insert()

View File

@@ -91,6 +91,7 @@ class PurchaseOrder(BuyingController):
self.party_account_currency = get_party_account_currency("Supplier", self.supplier, self.company) self.party_account_currency = get_party_account_currency("Supplier", self.supplier, self.company)
def validate_minimum_order_qty(self): def validate_minimum_order_qty(self):
if not self.get("items"): return
items = list(set([d.item_code for d in self.get("items")])) items = list(set([d.item_code for d in self.get("items")]))
itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty

View File

@@ -678,7 +678,7 @@ class BuyingController(StockController):
frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name)
def validate_schedule_date(self): def validate_schedule_date(self):
if not self.schedule_date: if not self.schedule_date and self.get("items"):
self.schedule_date = min([d.schedule_date for d in self.get("items")]) self.schedule_date = min([d.schedule_date for d in self.get("items")])
if self.schedule_date: if self.schedule_date:

View File

@@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext" source_link = "https://github.com/frappe/erpnext"
develop_version = '12.x.x-develop' develop_version = '12.x.x-develop'
staging_version = '11.0.3-beta.31' staging_version = '11.0.3-beta.32'
error_report_email = "support@erpnext.com" error_report_email = "support@erpnext.com"

View File

@@ -13,8 +13,8 @@ from frappe.model.document import Document
from erpnext.utilities.transaction_base import delete_events from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
class EmployeeUserDisabledError(frappe.ValidationError): class EmployeeUserDisabledError(frappe.ValidationError): pass
pass class EmployeeLeftValidationError(frappe.ValidationError): pass
class Employee(NestedSet): class Employee(NestedSet):
nsm_parent_field = 'reports_to' nsm_parent_field = 'reports_to'
@@ -62,8 +62,8 @@ class Employee(NestedSet):
def validate_user_details(self): def validate_user_details(self):
data = frappe.db.get_value('User', data = frappe.db.get_value('User',
self.user_id, ['enabled', 'user_image'], as_dict=1) self.user_id, ['enabled', 'user_image'], as_dict=1)
if data.get("user_image"):
self.image = data.get("user_image") self.image = data.get("user_image")
self.validate_for_enabled_user_id(data.get("enabled", 0)) self.validate_for_enabled_user_id(data.get("enabled", 0))
self.validate_duplicate_user_id() self.validate_duplicate_user_id()
@@ -147,8 +147,16 @@ class Employee(NestedSet):
validate_email_add(self.personal_email, True) validate_email_add(self.personal_email, True)
def validate_status(self): def validate_status(self):
if self.status == 'Left' and not self.relieving_date: if self.status == 'Left':
throw(_("Please enter relieving date.")) reports_to = frappe.db.get_all('Employee',
filters={'reports_to': self.name}
)
if reports_to:
link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name) for employee in reports_to]
throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee:&nbsp;")
+ ', '.join(link_to_employees), EmployeeLeftValidationError)
if not self.relieving_date:
throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled): def validate_for_enabled_user_id(self, enabled):
if not self.status == 'Active': if not self.status == 'Active':

View File

@@ -7,6 +7,7 @@ import frappe
import erpnext import erpnext
import unittest import unittest
import frappe.utils import frappe.utils
from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError
test_records = frappe.get_test_records('Employee') test_records = frappe.get_test_records('Employee')
@@ -32,6 +33,18 @@ class TestEmployee(unittest.TestCase):
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message) self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
def test_employee_status_left(self):
employee1 = make_employee("test_employee_1@company.com")
employee2 = make_employee("test_employee_2@company.com")
employee1_doc = frappe.get_doc("Employee", employee1)
employee2_doc = frappe.get_doc("Employee", employee2)
employee2_doc.reload()
employee2_doc.reports_to = employee1_doc.name
employee2_doc.save()
employee1_doc.reload()
employee1_doc.status = 'Left'
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
def make_employee(user): def make_employee(user):
if not frappe.db.get_value("User", user): if not frappe.db.get_value("User", user):
frappe.get_doc({ frappe.get_doc({

View File

@@ -14,7 +14,7 @@ frappe.ui.form.on("Leave Application", {
doctype: frm.doc.doctype doctype: frm.doc.doctype
} }
}; };
}); });
frm.set_query("employee", erpnext.queries.employee); frm.set_query("employee", erpnext.queries.employee);
}, },
@@ -83,7 +83,7 @@ frappe.ui.form.on("Leave Application", {
if (!frm.doc.employee && frappe.defaults.get_user_permissions()) { if (!frm.doc.employee && frappe.defaults.get_user_permissions()) {
const perm = frappe.defaults.get_user_permissions(); const perm = frappe.defaults.get_user_permissions();
if (perm && perm['Employee']) { if (perm && perm['Employee']) {
frm.set_value('employee', perm['Employee']["docs"][0]) frm.set_value('employee', perm['Employee'].map(perm_doc => perm_doc.doc)[0]);
} }
} }
}, },

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt, cint from frappe.utils import flt, cint, cstr
from frappe import _ from frappe import _
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document from frappe.model.document import Document
@@ -22,7 +22,7 @@ class SalaryStructure(Document):
overwritten_fields_if_missing = ["amount_based_on_formula", "formula", "amount"] overwritten_fields_if_missing = ["amount_based_on_formula", "formula", "amount"]
for table in ["earnings", "deductions"]: for table in ["earnings", "deductions"]:
for d in self.get(table): for d in self.get(table):
component_default_value = frappe.db.get_value("Salary Component", str(d.salary_component), component_default_value = frappe.db.get_value("Salary Component", cstr(d.salary_component),
overwritten_fields + overwritten_fields_if_missing, as_dict=1) overwritten_fields + overwritten_fields_if_missing, as_dict=1)
if component_default_value: if component_default_value:
for fieldname in overwritten_fields: for fieldname in overwritten_fields:

View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import getdate
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
from erpnext.hr.doctype.employee.test_employee import make_employee
class TestUploadAttendance(unittest.TestCase):
def test_date_range(self):
employee = make_employee("test_employee@company.com")
employee_doc = frappe.get_doc("Employee", employee)
date_of_joining = "2018-01-02"
relieving_date = "2018-01-03"
from_date = "2018-01-01"
to_date = "2018-01-04"
employee_doc.date_of_joining = date_of_joining
employee_doc.relieving_date = relieving_date
employee_doc.save()
args = {
"from_date": from_date,
"to_date": to_date
}
data = get_data(args)
filtered_data = []
for row in data:
if row[1] == employee:
filtered_data.append(row)
print(filtered_data)
for row in filtered_data:
self.assertTrue(getdate(row[3]) >= getdate(date_of_joining) and getdate(row[3]) <= getdate(relieving_date))

View File

@@ -41,16 +41,28 @@ def add_header(w):
return w return w
def add_data(w, args): def add_data(w, args):
data = get_data(args)
writedata(w, data)
return w
def get_data(args):
dates = get_dates(args) dates = get_dates(args)
employees = get_active_employees() employees = get_active_employees()
existing_attendance_records = get_existing_attendance_records(args) existing_attendance_records = get_existing_attendance_records(args)
data = []
for date in dates: for date in dates:
for employee in employees: for employee in employees:
if getdate(date) < getdate(employee.date_of_joining):
continue
if employee.relieving_date:
if getdate(date) > getdate(employee.relieving_date):
continue
existing_attendance = {} existing_attendance = {}
if existing_attendance_records \ if existing_attendance_records \
and tuple([getdate(date), employee.name]) in existing_attendance_records: and tuple([getdate(date), employee.name]) in existing_attendance_records \
and getdate(employee.date_of_joining) >= getdate(date) \
and getdate(employee.relieving_date) <= getdate(date):
existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])] existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])]
row = [ row = [
existing_attendance and existing_attendance.name or "", existing_attendance and existing_attendance.name or "",
employee.name, employee.employee_name, date, employee.name, employee.employee_name, date,
@@ -58,8 +70,12 @@ def add_data(w, args):
existing_attendance and existing_attendance.leave_type or "", employee.company, existing_attendance and existing_attendance.leave_type or "", employee.company,
existing_attendance and existing_attendance.naming_series or get_naming_series(), existing_attendance and existing_attendance.naming_series or get_naming_series(),
] ]
w.writerow(row) data.append(row)
return w return data
def writedata(w, data):
for row in data:
w.writerow(row)
def get_dates(args): def get_dates(args):
"""get list of dates in between from date and to date""" """get list of dates in between from date and to date"""
@@ -68,8 +84,13 @@ def get_dates(args):
return dates return dates
def get_active_employees(): def get_active_employees():
employees = frappe.db.sql("""select name, employee_name, company employees = frappe.db.get_all('Employee',
from tabEmployee where docstatus < 2 and status = 'Active'""", as_dict=1) fields=['name', 'employee_name', 'date_of_joining', 'company', 'relieving_date'],
filters={
'docstatus': ['<', 2],
'status': 'Active'
}
)
return employees return employees
def get_existing_attendance_records(args): def get_existing_attendance_records(args):

View File

@@ -79,67 +79,67 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "operation", "fieldname": "operation",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Item operation", "label": "Item operation",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Operation", "options": "Operation",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -966,6 +966,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "item_code.include_item_in_manufacturing", "fetch_from": "item_code.include_item_in_manufacturing",
"fieldname": "include_item_in_manufacturing",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@@ -974,6 +975,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Include Item In Manufacturing",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -987,29 +989,6 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"fieldname": "include_item_in_manufacturing",
"fieldtype": "Check",
"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": "Include Item In Manufacturing",
"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 "unique": 0
}, },
{ {
@@ -1044,71 +1023,6 @@
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operation",
"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": "Item operation",
"length": 0,
"no_copy": 0,
"options": "Operation",
"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
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Alternative Item",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@@ -1121,7 +1035,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-11-22 15:04:55.187136", "modified": "2018-12-28 16:38:56.529079",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Item", "name": "BOM Item",

View File

@@ -8,7 +8,19 @@ from frappe.model.utils.rename_field import rename_field
def execute(): def execute():
for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']: for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']:
if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'): if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'):
rename_field('BOM Item', "allow_transfer_for_manufacture", "include_item_in_manufacturing") if doctype != 'Item':
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
else:
frappe.reload_doc('stock', 'doctype', frappe.scrub(doctype))
rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'):
frappe.db.sql(""" UPDATE tabBOM
SET
allow_same_item_multiple_times = 0
WHERE
trim(coalesce(allow_same_item_multiple_times, '')) = '' """)
for doctype in ['BOM', 'Work Order']: for doctype in ['BOM', 'Work Order']:
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype)) frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))

View File

@@ -1,28 +1,60 @@
import frappe import frappe
from frappe.desk.form.linked_with import get_linked_doctypes
# Skips user permission check for doctypes where department link field was recently added # Skips user permission check for doctypes where department link field was recently added
# https://github.com/frappe/erpnext/pull/14121 # https://github.com/frappe/erpnext/pull/14121
def execute(): def execute():
user_permissions = frappe.get_all("User Permission", doctypes_to_skip = []
filters=[['allow', '=', 'Department']], for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip',
fields=['name', 'skip_for_doctype']) 'Attendance', 'Training Feedback', 'Training Result Employee',
'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee',
'Timesheet', 'Sales Person', 'Payroll Employee Detail']:
if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue
doctypes_to_skip.append(doctype)
doctypes_to_skip = [] frappe.reload_doctype('User Permission')
for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip', user_permissions = frappe.get_all("User Permission",
'Attendance', 'Training Feedback', 'Training Result Employee', filters=[['allow', '=', 'Department'], ['applicable_for', 'in', [None] + doctypes_to_skip]],
'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee', fields=['name', 'applicable_for'])
'Timesheet', 'Sales Person', 'Payroll Employee Detail']:
if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue
doctypes_to_skip.append(doctype)
for perm in user_permissions: user_permissions_to_delete = []
skip_for_doctype = perm.get('skip_for_doctype') new_user_permissions_list = []
skip_for_doctype = skip_for_doctype.split('\n') + doctypes_to_skip for user_permission in user_permissions:
skip_for_doctype = set(skip_for_doctype) # to remove duplicates if user_permission.applicable_for:
skip_for_doctype = '\n'.join(skip_for_doctype) # convert back to string # simply delete user permission record since it needs to be skipped.
user_permissions_to_delete.append(user_permission.name)
else:
# if applicable_for is `None` it means that user permission is applicable for every doctype
# to avoid this we need to create other user permission records and only skip the listed doctypes in this patch
linked_doctypes = get_linked_doctypes(user_permission.allow, True).keys()
applicable_for_doctypes = list(set(linked_doctypes) - set(doctypes_to_skip))
frappe.set_value('User Permission', perm.name, 'skip_for_doctype', skip_for_doctype) user_permissions_to_delete.append(user_permission.name)
for doctype in applicable_for_doctypes:
if doctype:
# Maintain sequence (name, user, allow, for_value, applicable_for, apply_to_all_doctypes)
new_user_permissions_list.append((
frappe.generate_hash("", 10),
user_permission.user,
user_permission.allow,
user_permission.for_value,
doctype,
0
))
if new_user_permissions_list:
frappe.db.sql('''
INSERT INTO `tabUser Permission`
(`name`, `user`, `allow`, `for_value`, `applicable_for`, `apply_to_all_doctypes`)
VALUES {}'''.format(', '.join(['%s'] * len(new_user_permissions_list))), # nosec
tuple(new_user_permissions_list)
)
if user_permissions_to_delete:
frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `name` IN ({})'.format( # nosec
','.join(['%s'] * len(user_permissions_to_delete))
), tuple(user_permissions_to_delete))

View File

@@ -258,13 +258,13 @@ class Project(Document):
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0 self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0
def update_sales_amount(self): def update_sales_amount(self):
total_sales_amount = frappe.db.sql("""select sum(base_grand_total) total_sales_amount = frappe.db.sql("""select sum(base_net_total)
from `tabSales Order` where project = %s and docstatus=1""", self.name) from `tabSales Order` where project = %s and docstatus=1""", self.name)
self.total_sales_amount = total_sales_amount and total_sales_amount[0][0] or 0 self.total_sales_amount = total_sales_amount and total_sales_amount[0][0] or 0
def update_billed_amount(self): def update_billed_amount(self):
total_billed_amount = frappe.db.sql("""select sum(base_grand_total) total_billed_amount = frappe.db.sql("""select sum(base_net_total)
from `tabSales Invoice` where project = %s and docstatus=1""", self.name) from `tabSales Invoice` where project = %s and docstatus=1""", self.name)
self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0 self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0

View File

@@ -97,6 +97,9 @@ erpnext.setup.slides_settings = [
if (!this.values.company_abbr) { if (!this.values.company_abbr) {
return false; return false;
} }
if (this.values.company_abbr.length > 5) {
return false;
}
return true; return true;
} }
}, },

View File

@@ -256,7 +256,7 @@ $.extend(erpnext.utils, {
let unscrub_option = frappe.model.unscrub(option); let unscrub_option = frappe.model.unscrub(option);
let user_permission = frappe.defaults.get_user_permissions(); let user_permission = frappe.defaults.get_user_permissions();
if(user_permission && user_permission[unscrub_option]) { if(user_permission && user_permission[unscrub_option]) {
return user_permission[unscrub_option]["docs"]; return user_permission[unscrub_option].map(perm => perm.doc);
} else { } else {
return $.map(locals[`:${unscrub_option}`], function(c) { return c.name; }).sort(); return $.map(locals[`:${unscrub_option}`], function(c) { return c.name; }).sort();
} }

View File

@@ -1,6 +1,11 @@
Selling management module. Includes forms for capturing / managing the sales process. Selling management module. Includes forms for capturing / managing the sales process:
- Customer
- Campaign
- Quotation
- Sales Order
Moved to CRM Module:
- Lead - Lead
- Opportunity - Opportunity
- Quotation
- Sales Order

View File

@@ -871,10 +871,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -993,6 +995,7 @@
"label": "Net Rate", "label": "Net Rate",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "currency",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,
@@ -1910,7 +1913,7 @@
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2018-08-22 16:15:52.750381", "modified": "2018-12-12 05:52:46.135944",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation Item", "name": "Quotation Item",
@@ -1925,4 +1928,4 @@
"track_changes": 1, "track_changes": 1,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@@ -623,7 +623,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
def update_item(source, target, source_parent): def update_item(source, target, source_parent):
target.amount = flt(source.amount) - flt(source.billed_amt) target.amount = flt(source.amount) - flt(source.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate) target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = target.amount / flt(source.rate) if (source.rate and source.billed_amt) else source.qty target.qty = target.amount / flt(source.rate) if (source.rate and source.billed_amt) else source.qty - source.returned_qty
if source_parent.project: if source_parent.project:
target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center")

View File

@@ -391,8 +391,24 @@ def get_invoiced_qty_map(delivery_note):
return invoiced_qty_map return invoiced_qty_map
def get_returned_qty_map(sales_orders):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = {}
for name, returned_qty in frappe.get_all('Sales Order Item', fields = ["name", "returned_qty"],
filters = {'parent': ('in', sales_orders), 'docstatus': 1}, as_list=1):
if not returned_qty_map.get(name):
returned_qty_map[name] = 0
returned_qty_map[name] += returned_qty
return returned_qty_map
@frappe.whitelist() @frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None): def make_sales_invoice(source_name, target_doc=None):
doc = frappe.get_doc('Delivery Note', source_name)
sales_orders = [d.against_sales_order for d in doc.items]
returned_qty_map = get_returned_qty_map(sales_orders)
invoiced_qty_map = get_invoiced_qty_map(source_name) invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target): def set_missing_values(source, target):
@@ -412,7 +428,9 @@ def make_sales_invoice(source_name, target_doc=None):
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address)) target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0) target_doc.qty = (source_doc.qty -
invoiced_qty_map.get(source_doc.name, 0) - returned_qty_map.get(source_doc.so_detail, 0))
if source_doc.serial_no and source_parent.per_billed > 0: if source_doc.serial_no and source_parent.per_billed > 0:
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code, target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
target_doc.qty, source_parent.name) target_doc.qty, source_parent.name)

View File

@@ -1,7 +1,8 @@
frappe.listview_settings['Delivery Note'] = { frappe.listview_settings['Delivery Note'] = {
add_fields: ["grand_total", "is_return", "per_billed", "status", "currency"], add_fields: ["customer", "customer_name", "base_grand_total", "per_installed", "per_billed",
get_indicator: function (doc) { "transporter_name", "grand_total", "is_return", "status", "currency"],
if (cint(doc.is_return) == 1) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if (doc.status === "Closed") { } else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"]; return [__("Closed"), "green", "status,=,Closed"];

View File

@@ -636,6 +636,24 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
set_perpetual_inventory(0, company) set_perpetual_inventory(0, company)
def test_make_sales_invoice_from_dn_for_returned_qty(self):
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
so = make_sales_order(qty=2)
so.submit()
dn = make_delivery_note(so.name)
dn.submit()
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-1, do_not_submit=True)
dn1.items[0].against_sales_order = so.name
dn1.items[0].so_detail = so.items[0].name
dn1.submit()
si = make_sales_invoice(dn.name)
self.assertEquals(si.items[0].qty, 1)
def create_delivery_note(**args): def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note") dn = frappe.new_doc("Delivery Note")

View File

@@ -1,7 +1,8 @@
frappe.listview_settings['Purchase Receipt'] = { frappe.listview_settings['Purchase Receipt'] = {
add_fields: ["is_return", "grand_total", "status", "per_billed"], add_fields: ["supplier", "supplier_name", "base_grand_total", "is_subcontracted",
get_indicator: function (doc) { "transporter_name", "is_return", "status", "per_billed", "currency"],
if (cint(doc.is_return) == 1) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if (doc.status === "Closed") { } else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"]; return [__("Closed"), "green", "status,=,Closed"];

View File

@@ -94,11 +94,10 @@ def validate_filters(filters):
filters["company"] = frappe.defaults.get_user_default("Company") filters["company"] = frappe.defaults.get_user_default("Company")
def get_warehouse_list(filters): def get_warehouse_list(filters):
from frappe.defaults import get_user_permissions from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
condition = '' condition = ''
user_permitted_warehouse = filter(None, get_user_permissions() user_permitted_warehouse = get_permitted_documents('Warehouse')
.get("Warehouse", {})
.get("docs", []))
value = () value = ()
if user_permitted_warehouse: if user_permitted_warehouse:
condition = "and name in %s" condition = "and name in %s"