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 return amount * tax_details.rate / 100
if tax_details.cumulative_threshold:
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] vouchers = [d.voucher_no for d in entries]
vouchers += get_advance_vouchers(ref_doc.supplier, fiscal_year) advance_vouchers = get_advance_vouchers(ref_doc.supplier, fiscal_year)
tds_deducted = 0 tds_vouchers = vouchers + advance_vouchers
if vouchers:
tds_deducted = flt(frappe.db.sql(""" if tds_vouchers:
select sum(credit) tds_deducted = frappe.db.sql("""
from `tabGL Entry` SELECT sum(credit) FROM `tabGL Entry`
where account=%s and fiscal_year=%s and credit > 0 WHERE
and voucher_no in ({0}) account=%s and fiscal_year=%s and credit > 0
""".format(', '.join(["'%s'" % d for d in vouchers])), and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))),
(tax_details.account_head, fiscal_year))[0][0]) ((tax_details.account_head, fiscal_year) + tuple(tds_vouchers)))
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 = ""

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,50 +15,34 @@ 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):
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194D - Individual")
pi = create_purchase_invoice()
pi.submit()
self.assertEqual(pi.taxes_and_charges_deducted, 800)
self.assertEqual(pi.grand_total, 15200)
# check gl entry for the purchase invoice
gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"])
self.assertEqual(len(gl_entries), 3)
for d in gl_entries:
if d.account == pi.credit_to:
self.assertEqual(d.credit, 15200)
elif d.account == pi.items[0].get("expense_account"):
self.assertEqual(d.debit, 16000)
elif d.account == pi.taxes[0].get("account_head"):
self.assertEqual(d.credit, 800)
else:
raise ValueError("Account head does not match.")
# delete purchase invoice to avoid it interefering in other tests
pi.cancel()
frappe.delete_doc('Purchase Invoice', pi.name)
def test_cumulative_threshold_tds(self): def test_cumulative_threshold_tds(self):
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194C - Individual") frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS")
invoices = [] invoices = []
# create invoices for lower than single threshold tax rate # create invoices for lower than single threshold tax rate
for _ in xrange(6): for _ in xrange(2):
pi = create_purchase_invoice() pi = create_purchase_invoice(supplier = "Test TDS Supplier")
pi.submit() pi.submit()
invoices.append(pi) invoices.append(pi)
# create another invoice whose total when added to previously created invoice, # create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold # surpasses cumulative threshhold
pi = create_purchase_invoice() pi = create_purchase_invoice(supplier = "Test TDS Supplier")
pi.submit() pi.submit()
# assert equal tax deduction on total invoice amount uptil now # assert equal tax deduction on total invoice amount uptil now
self.assertEqual(pi.taxes_and_charges_deducted, 1120) self.assertEqual(pi.taxes_and_charges_deducted, 3000)
self.assertEqual(pi.grand_total, 14880) 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) invoices.append(pi)
#delete invoices to avoid clashing #delete invoices to avoid clashing
@@ -65,14 +50,70 @@ class TestTaxWithholdingCategory(unittest.TestCase):
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(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
gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"])
self.assertEqual(len(gl_entries), 3)
for d in gl_entries:
if d.account == pi.credit_to:
self.assertEqual(d.credit, 18000)
elif d.account == pi.items[0].get("expense_account"):
self.assertEqual(d.debit, 20000)
elif d.account == pi.taxes[0].get("account_head"):
self.assertEqual(d.credit, 2000)
else:
raise ValueError("Account head does not match.")
pi = create_purchase_invoice(supplier = "Test TDS Supplier1")
pi.submit()
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
for d in invoices:
d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
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
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
if frappe.db.exists('Supplier', name):
continue
frappe.get_doc({ frappe.get_doc({
"supplier_group": "_Test Supplier Group", "supplier_group": "_Test Supplier Group",
"supplier_name": "Test TDS Supplier", "supplier_name": name,
"doctype": "Supplier", "doctype": "Supplier",
"tax_withholding_category": "TDS - 194D - Individual"
}).insert() }).insert()
# create an item # create an item
if not frappe.db.exists('Item', "TDS Item"):
frappe.get_doc({ frappe.get_doc({
"doctype": "Item", "doctype": "Item",
"item_code": "TDS Item", "item_code": "TDS Item",
"item_name": "TDS Item", "item_name": "TDS Item",
"item_group": "All Item Groups", "item_group": "All Item Groups",
"company": "_Test Company",
"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

@@ -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,7 +62,7 @@ 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,7 +147,15 @@ 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':
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.")) throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled): def validate_for_enabled_user_id(self, enabled):

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

@@ -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(),
] ]
data.append(row)
return data
def writedata(w, data):
for row in data:
w.writerow(row) w.writerow(row)
return w
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

@@ -966,27 +966,6 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "item_code.include_item_in_manufacturing", "fetch_from": "item_code.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,
"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,
"fieldname": "include_item_in_manufacturing", "fieldname": "include_item_in_manufacturing",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 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,15 +1,11 @@
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",
filters=[['allow', '=', 'Department']],
fields=['name', 'skip_for_doctype'])
doctypes_to_skip = [] doctypes_to_skip = []
for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip', for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip',
'Attendance', 'Training Feedback', 'Training Result Employee', 'Attendance', 'Training Feedback', 'Training Result Employee',
'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee', 'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee',
@@ -17,12 +13,48 @@ def execute():
if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue
doctypes_to_skip.append(doctype) doctypes_to_skip.append(doctype)
for perm in user_permissions: frappe.reload_doctype('User Permission')
skip_for_doctype = perm.get('skip_for_doctype')
skip_for_doctype = skip_for_doctype.split('\n') + doctypes_to_skip user_permissions = frappe.get_all("User Permission",
skip_for_doctype = set(skip_for_doctype) # to remove duplicates filters=[['allow', '=', 'Department'], ['applicable_for', 'in', [None] + doctypes_to_skip]],
skip_for_doctype = '\n'.join(skip_for_doctype) # convert back to string fields=['name', 'applicable_for'])
frappe.set_value('User Permission', perm.name, 'skip_for_doctype', skip_for_doctype) user_permissions_to_delete = []
new_user_permissions_list = []
for user_permission in user_permissions:
if user_permission.applicable_for:
# 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))
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",

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,5 +1,6 @@
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",
"transporter_name", "grand_total", "is_return", "status", "currency"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) { if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];

View File

@@ -637,6 +637,24 @@ class TestDeliveryNote(unittest.TestCase):
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")
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -1,5 +1,6 @@
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",
"transporter_name", "is_return", "status", "per_billed", "currency"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) { if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];

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"