mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-26 05:15:20 +00:00
@@ -145,8 +145,8 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
|
||||
|
||||
loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor)
|
||||
|
||||
if loyalty_amount > ref_doc.grand_total:
|
||||
frappe.throw(_("You can't redeem Loyalty Points having more value than the Grand Total."))
|
||||
if loyalty_amount > ref_doc.rounded_total:
|
||||
frappe.throw(_("You can't redeem Loyalty Points having more value than the Rounded Total."))
|
||||
|
||||
if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount:
|
||||
ref_doc.loyalty_amount = loyalty_amount
|
||||
|
||||
@@ -153,7 +153,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
frappe.ui.form.on('POS Closing Entry Detail', {
|
||||
closing_amount: (frm, cdt, cdn) => {
|
||||
const row = locals[cdt][cdn];
|
||||
frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount));
|
||||
frappe.model.set_value(cdt, cdn, "difference", flt(row.closing_amount - row.expected_amount));
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3376,6 +3376,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||
|
||||
@change_settings("Selling Settings", {"allow_negative_rates_for_items": 0})
|
||||
def test_sales_return_negative_rate(self):
|
||||
si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True)
|
||||
self.assertRaises(frappe.ValidationError, si.save)
|
||||
|
||||
@@ -262,14 +262,20 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
if tax_deducted:
|
||||
net_total = inv.tax_withholding_net_total
|
||||
if ldc:
|
||||
tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total)
|
||||
limit_consumed = get_limit_consumed(ldc, parties)
|
||||
if is_valid_certificate(ldc, posting_date, limit_consumed):
|
||||
tax_amount = get_lower_deduction_amount(
|
||||
net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details
|
||||
)
|
||||
else:
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
else:
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
|
||||
# once tds is deducted, not need to add vouchers in the invoice
|
||||
voucher_wise_amount = {}
|
||||
else:
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers)
|
||||
|
||||
elif party_type == "Customer":
|
||||
if tax_deducted:
|
||||
@@ -416,7 +422,7 @@ def get_deducted_tax(taxable_vouchers, tax_details):
|
||||
return sum(entries)
|
||||
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
|
||||
|
||||
@@ -496,15 +502,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
net_total += inv.tax_withholding_net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from,
|
||||
ldc.valid_upto,
|
||||
inv.get("posting_date") or inv.get("transaction_date"),
|
||||
tax_deducted,
|
||||
inv.tax_withholding_net_total,
|
||||
ldc.certificate_limit,
|
||||
):
|
||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):
|
||||
tds_amount = get_lower_deduction_amount(
|
||||
supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details
|
||||
)
|
||||
else:
|
||||
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
||||
|
||||
@@ -582,8 +583,7 @@ def get_invoice_total_without_tcs(inv, tax_details):
|
||||
return inv.grand_total - tcs_tax_row_amount
|
||||
|
||||
|
||||
def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
||||
tds_amount = 0
|
||||
def get_limit_consumed(ldc, parties):
|
||||
limit_consumed = frappe.db.get_value(
|
||||
"Purchase Invoice",
|
||||
{
|
||||
@@ -597,37 +597,29 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
||||
"sum(tax_withholding_net_total)",
|
||||
)
|
||||
|
||||
if is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(
|
||||
net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details
|
||||
)
|
||||
|
||||
return tds_amount
|
||||
return limit_consumed
|
||||
|
||||
|
||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||
if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
|
||||
def get_lower_deduction_amount(
|
||||
current_amount, limit_consumed, certificate_limit, rate, tax_details
|
||||
):
|
||||
if certificate_limit - flt(limit_consumed) - flt(current_amount) >= 0:
|
||||
return current_amount * rate / 100
|
||||
else:
|
||||
ltds_amount = certificate_limit - flt(deducted_amount)
|
||||
ltds_amount = certificate_limit - flt(limit_consumed)
|
||||
tds_amount = current_amount - ltds_amount
|
||||
|
||||
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
|
||||
|
||||
|
||||
def is_valid_certificate(
|
||||
valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit
|
||||
):
|
||||
valid = False
|
||||
def is_valid_certificate(ldc, posting_date, limit_consumed):
|
||||
available_amount = flt(ldc.certificate_limit) - flt(limit_consumed)
|
||||
if (
|
||||
getdate(ldc.valid_from) <= getdate(posting_date) <= getdate(ldc.valid_upto)
|
||||
) and available_amount > 0:
|
||||
return True
|
||||
|
||||
available_amount = flt(certificate_limit) - flt(deducted_amount)
|
||||
|
||||
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
||||
valid = True
|
||||
|
||||
return valid
|
||||
return False
|
||||
|
||||
|
||||
def normal_round(number):
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.utils import today
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@@ -17,6 +18,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
# create relevant supplier, etc
|
||||
create_records()
|
||||
create_tax_withholding_category_records()
|
||||
make_pan_no_field()
|
||||
|
||||
def tearDown(self):
|
||||
cancel_invoices()
|
||||
@@ -451,6 +453,40 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
pe2.cancel()
|
||||
pe3.cancel()
|
||||
|
||||
def test_lower_deduction_certificate_application(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier",
|
||||
"Test LDC Supplier",
|
||||
{
|
||||
"tax_withholding_category": "Test Service Category",
|
||||
"pan": "ABCTY1234D",
|
||||
},
|
||||
)
|
||||
|
||||
create_lower_deduction_certificate(
|
||||
supplier="Test LDC Supplier",
|
||||
certificate_no="1AE0423AAJ",
|
||||
tax_withholding_category="Test Service Category",
|
||||
tax_rate=2,
|
||||
limit=50000,
|
||||
)
|
||||
|
||||
pi1 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000)
|
||||
pi1.submit()
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 700)
|
||||
|
||||
pi2 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000)
|
||||
pi2.submit()
|
||||
self.assertEqual(pi2.taxes[0].tax_amount, 2300)
|
||||
|
||||
pi3 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000)
|
||||
pi3.submit()
|
||||
self.assertEqual(pi3.taxes[0].tax_amount, 3500)
|
||||
|
||||
pi1.cancel()
|
||||
pi2.cancel()
|
||||
pi3.cancel()
|
||||
|
||||
|
||||
def cancel_invoices():
|
||||
purchase_invoices = frappe.get_all(
|
||||
@@ -610,6 +646,7 @@ def create_records():
|
||||
"Test TDS Supplier6",
|
||||
"Test TDS Supplier7",
|
||||
"Test TDS Supplier8",
|
||||
"Test LDC Supplier",
|
||||
]:
|
||||
if frappe.db.exists("Supplier", name):
|
||||
continue
|
||||
@@ -806,3 +843,39 @@ def create_tax_withholding_category(
|
||||
"accounts": [{"company": "_Test Company", "account": account}],
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_lower_deduction_certificate(
|
||||
supplier, tax_withholding_category, tax_rate, certificate_no, limit
|
||||
):
|
||||
fiscal_year = get_fiscal_year(today(), company="_Test Company")
|
||||
if not frappe.db.exists("Lower Deduction Certificate", certificate_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Lower Deduction Certificate",
|
||||
"company": "_Test Company",
|
||||
"supplier": supplier,
|
||||
"certificate_no": certificate_no,
|
||||
"tax_withholding_category": tax_withholding_category,
|
||||
"fiscal_year": fiscal_year[0],
|
||||
"valid_from": fiscal_year[1],
|
||||
"valid_upto": fiscal_year[2],
|
||||
"rate": tax_rate,
|
||||
"certificate_limit": limit,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_pan_no_field():
|
||||
pan_field = {
|
||||
"Supplier": [
|
||||
{
|
||||
"fieldname": "pan",
|
||||
"label": "PAN",
|
||||
"fieldtype": "Data",
|
||||
"translatable": 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(pan_field, update=1)
|
||||
|
||||
@@ -436,12 +436,11 @@ class ReceivablePayableReport(object):
|
||||
def allocate_outstanding_based_on_payment_terms(self, row):
|
||||
self.get_payment_terms(row)
|
||||
for term in row.payment_terms:
|
||||
|
||||
# update "paid" and "oustanding" for this term
|
||||
# update "paid" and "outstanding" for this term
|
||||
if not term.paid:
|
||||
self.allocate_closing_to_term(row, term, "paid")
|
||||
|
||||
# update "credit_note" and "oustanding" for this term
|
||||
# update "credit_note" and "outstanding" for this term
|
||||
if term.outstanding:
|
||||
self.allocate_closing_to_term(row, term, "credit_note")
|
||||
|
||||
@@ -453,7 +452,8 @@ class ReceivablePayableReport(object):
|
||||
"""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount,
|
||||
ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@@ -469,6 +469,10 @@ class ReceivablePayableReport(object):
|
||||
original_row = frappe._dict(row)
|
||||
row.payment_terms = []
|
||||
|
||||
# Advance allocated during invoicing is not considered in payment terms
|
||||
# Deduct that from paid amount pre allocation
|
||||
row.paid -= flt(payment_terms_details[0].total_advance)
|
||||
|
||||
# If no or single payment terms, no need to split the row
|
||||
if len(payment_terms_details) <= 1:
|
||||
return
|
||||
@@ -483,7 +487,7 @@ class ReceivablePayableReport(object):
|
||||
) and d.currency == d.party_account_currency:
|
||||
invoiced = d.payment_amount
|
||||
else:
|
||||
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
|
||||
invoiced = d.base_payment_amount
|
||||
|
||||
row.payment_terms.append(
|
||||
term.update(
|
||||
|
||||
@@ -33,7 +33,14 @@ frappe.query_reports["Tax Withholding Details"] = {
|
||||
frappe.throw(__("Please select Party Type first"));
|
||||
}
|
||||
return party_type;
|
||||
}
|
||||
},
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"tax_withholding_category": ["!=",""],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
|
||||
@@ -7,19 +7,26 @@ from frappe import _
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
else:
|
||||
party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
filters.update({"naming_series": party_naming_by})
|
||||
|
||||
validate_filters(filters)
|
||||
(
|
||||
tds_docs,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
invoice_net_total_map,
|
||||
net_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
|
||||
)
|
||||
return columns, res
|
||||
|
||||
@@ -31,7 +38,7 @@ def validate_filters(filters):
|
||||
|
||||
|
||||
def get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
|
||||
):
|
||||
party_map = get_party_pan_map(filters.get("party_type"))
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
@@ -39,7 +46,7 @@ def get_result(
|
||||
|
||||
out = []
|
||||
for name, details in gle_map.items():
|
||||
tax_amount, total_amount = 0, 0
|
||||
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
||||
tax_withholding_category = tax_category_map.get(name)
|
||||
rate = tax_rate_map.get(tax_withholding_category)
|
||||
|
||||
@@ -60,8 +67,8 @@ def get_result(
|
||||
if entry.account in tds_accounts:
|
||||
tax_amount += entry.credit - entry.debit
|
||||
|
||||
if invoice_net_total_map.get(name):
|
||||
total_amount = invoice_net_total_map.get(name)
|
||||
if net_total_map.get(name):
|
||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||
else:
|
||||
total_amount += entry.credit
|
||||
|
||||
@@ -69,15 +76,13 @@ def get_result(
|
||||
if party_map.get(party, {}).get("party_type") == "Supplier":
|
||||
party_name = "supplier_name"
|
||||
party_type = "supplier_type"
|
||||
table_name = "Supplier"
|
||||
else:
|
||||
party_name = "customer_name"
|
||||
party_type = "customer_type"
|
||||
table_name = "Customer"
|
||||
|
||||
row = {
|
||||
"pan"
|
||||
if frappe.db.has_column(table_name, "pan")
|
||||
if frappe.db.has_column(filters.party_type, "pan")
|
||||
else "tax_id": party_map.get(party, {}).get("pan"),
|
||||
"party": party_map.get(party, {}).get("name"),
|
||||
}
|
||||
@@ -91,6 +96,8 @@ def get_result(
|
||||
"entity_type": party_map.get(party, {}).get(party_type),
|
||||
"rate": rate,
|
||||
"total_amount": total_amount,
|
||||
"grand_total": grand_total,
|
||||
"base_total": base_total,
|
||||
"tax_amount": tax_amount,
|
||||
"transaction_date": posting_date,
|
||||
"transaction_type": voucher_type,
|
||||
@@ -144,9 +151,9 @@ def get_gle_map(documents):
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
columns = [
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
@@ -158,25 +165,30 @@ def get_columns(filters):
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
columns.append(
|
||||
{"label": _("Party Name"), "fieldname": "party_name", "fieldtype": "Data", "width": 180}
|
||||
{
|
||||
"label": _(filters.party_type + " Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 180,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 120},
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
@@ -184,15 +196,27 @@ def get_columns(filters):
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"),
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Base Total"),
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
|
||||
@@ -216,7 +240,7 @@ def get_tds_docs(filters):
|
||||
payment_entries = []
|
||||
journal_entries = []
|
||||
tax_category_map = frappe._dict()
|
||||
invoice_net_total_map = frappe._dict()
|
||||
net_total_map = frappe._dict()
|
||||
or_filters = frappe._dict()
|
||||
journal_entry_party_map = frappe._dict()
|
||||
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
|
||||
@@ -260,13 +284,13 @@ def get_tds_docs(filters):
|
||||
tds_documents.append(d.voucher_no)
|
||||
|
||||
if purchase_invoices:
|
||||
get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map)
|
||||
get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, net_total_map)
|
||||
|
||||
if sales_invoices:
|
||||
get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, invoice_net_total_map)
|
||||
get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, net_total_map)
|
||||
|
||||
if payment_entries:
|
||||
get_doc_info(payment_entries, "Payment Entry", tax_category_map)
|
||||
get_doc_info(payment_entries, "Payment Entry", tax_category_map, net_total_map)
|
||||
|
||||
if journal_entries:
|
||||
journal_entry_party_map = get_journal_entry_party_map(journal_entries)
|
||||
@@ -277,7 +301,7 @@ def get_tds_docs(filters):
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
invoice_net_total_map,
|
||||
net_total_map,
|
||||
)
|
||||
|
||||
|
||||
@@ -295,11 +319,25 @@ def get_journal_entry_party_map(journal_entries):
|
||||
return journal_entry_party_map
|
||||
|
||||
|
||||
def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None):
|
||||
def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
if doctype == "Purchase Invoice":
|
||||
fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"]
|
||||
if doctype == "Sales Invoice":
|
||||
fields = ["name", "base_net_total"]
|
||||
fields = [
|
||||
"name",
|
||||
"tax_withholding_category",
|
||||
"base_tax_withholding_net_total",
|
||||
"grand_total",
|
||||
"base_total",
|
||||
]
|
||||
elif doctype == "Sales Invoice":
|
||||
fields = ["name", "base_net_total", "grand_total", "base_total"]
|
||||
elif doctype == "Payment Entry":
|
||||
fields = [
|
||||
"name",
|
||||
"tax_withholding_category",
|
||||
"paid_amount",
|
||||
"paid_amount_after_tax",
|
||||
"base_paid_amount",
|
||||
]
|
||||
else:
|
||||
fields = ["name", "tax_withholding_category"]
|
||||
|
||||
@@ -308,9 +346,15 @@ def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None
|
||||
for entry in entries:
|
||||
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
||||
if doctype == "Purchase Invoice":
|
||||
invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total})
|
||||
if doctype == "Sales Invoice":
|
||||
invoice_net_total_map.update({entry.name: entry.base_net_total})
|
||||
net_total_map.update(
|
||||
{entry.name: [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]}
|
||||
)
|
||||
elif doctype == "Sales Invoice":
|
||||
net_total_map.update({entry.name: [entry.base_net_total, entry.grand_total, entry.base_total]})
|
||||
elif doctype == "Payment Entry":
|
||||
net_total_map.update(
|
||||
{entry.name: [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]}
|
||||
)
|
||||
|
||||
|
||||
def get_tax_rate_map(filters):
|
||||
|
||||
@@ -12,17 +12,35 @@ frappe.query_reports["TDS Computation Summary"] = {
|
||||
"default": frappe.defaults.get_default('company')
|
||||
},
|
||||
{
|
||||
"fieldname":"supplier",
|
||||
"label": __("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Supplier", "Customer"],
|
||||
"reqd": 1,
|
||||
"default": "Supplier",
|
||||
"on_change": function(){
|
||||
frappe.query_report.set_filter_value("party", "");
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"get_options": function() {
|
||||
var party_type = frappe.query_report.get_filter_value('party_type');
|
||||
var party = frappe.query_report.get_filter_value('party');
|
||||
if(party && !party_type) {
|
||||
frappe.throw(__("Please select Party Type first"));
|
||||
}
|
||||
return party_type;
|
||||
},
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"tax_withholding_category": ["!=",""],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
|
||||
@@ -9,9 +9,14 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
validate_filters(filters)
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
else:
|
||||
party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
filters.update({"naming_series": party_naming_by})
|
||||
|
||||
validate_filters(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
(
|
||||
@@ -25,7 +30,7 @@ def execute(filters=None):
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
|
||||
)
|
||||
final_result = group_by_supplier_and_category(res)
|
||||
final_result = group_by_party_and_category(res, filters)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -43,60 +48,67 @@ def validate_filters(filters):
|
||||
filters["fiscal_year"] = from_year
|
||||
|
||||
|
||||
def group_by_supplier_and_category(data):
|
||||
supplier_category_wise_map = {}
|
||||
def group_by_party_and_category(data, filters):
|
||||
party_category_wise_map = {}
|
||||
|
||||
for row in data:
|
||||
supplier_category_wise_map.setdefault(
|
||||
(row.get("supplier"), row.get("section_code")),
|
||||
party_category_wise_map.setdefault(
|
||||
(row.get("party"), row.get("section_code")),
|
||||
{
|
||||
"pan": row.get("pan"),
|
||||
"supplier": row.get("supplier"),
|
||||
"supplier_name": row.get("supplier_name"),
|
||||
"tax_id": row.get("tax_id"),
|
||||
"party": row.get("party"),
|
||||
"party_name": row.get("party_name"),
|
||||
"section_code": row.get("section_code"),
|
||||
"entity_type": row.get("entity_type"),
|
||||
"tds_rate": row.get("tds_rate"),
|
||||
"total_amount_credited": 0.0,
|
||||
"tds_deducted": 0.0,
|
||||
"rate": row.get("rate"),
|
||||
"total_amount": 0.0,
|
||||
"tax_amount": 0.0,
|
||||
},
|
||||
)
|
||||
|
||||
supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
|
||||
"total_amount_credited"
|
||||
] += row.get("total_amount_credited", 0.0)
|
||||
party_category_wise_map.get((row.get("party"), row.get("section_code")))[
|
||||
"total_amount"
|
||||
] += row.get("total_amount", 0.0)
|
||||
|
||||
supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
|
||||
"tds_deducted"
|
||||
] += row.get("tds_deducted", 0.0)
|
||||
party_category_wise_map.get((row.get("party"), row.get("section_code")))[
|
||||
"tax_amount"
|
||||
] += row.get("tax_amount", 0.0)
|
||||
|
||||
final_result = get_final_result(supplier_category_wise_map)
|
||||
final_result = get_final_result(party_category_wise_map)
|
||||
|
||||
return final_result
|
||||
|
||||
|
||||
def get_final_result(supplier_category_wise_map):
|
||||
def get_final_result(party_category_wise_map):
|
||||
out = []
|
||||
for key, value in supplier_category_wise_map.items():
|
||||
for key, value in party_category_wise_map.items():
|
||||
out.append(value)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
columns = [
|
||||
{"label": _("PAN"), "fieldname": "pan", "fieldtype": "Data", "width": 90},
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
||||
{
|
||||
"label": _("Supplier"),
|
||||
"options": "Supplier",
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
},
|
||||
]
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
columns.append(
|
||||
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180}
|
||||
{
|
||||
"label": _(filters.party_type + " Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
@@ -109,18 +121,23 @@ def get_columns(filters):
|
||||
"width": 180,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
|
||||
{"label": _("TDS Rate %"), "fieldname": "tds_rate", "fieldtype": "Percent", "width": 90},
|
||||
{
|
||||
"label": _("Total Amount Credited"),
|
||||
"fieldname": "total_amount_credited",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Amount of TDS Deducted"),
|
||||
"fieldname": "tds_deducted",
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import comma_or, flt, getdate, now, nowdate
|
||||
from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate
|
||||
|
||||
|
||||
class OverAllowanceError(frappe.ValidationError):
|
||||
@@ -233,8 +233,17 @@ class StatusUpdater(Document):
|
||||
if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
|
||||
|
||||
if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0:
|
||||
frappe.throw(_("For an item {0}, rate must be a positive number").format(d.item_code))
|
||||
if not frappe.db.get_single_value("Selling Settings", "allow_negative_rates_for_items"):
|
||||
if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"For item {0}, rate must be a positive number. To Allow negative rates, enable {1} in {2}"
|
||||
).format(
|
||||
frappe.bold(d.item_code),
|
||||
frappe.bold(_("`Allow Negative rates for Items`")),
|
||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||
),
|
||||
)
|
||||
|
||||
if d.doctype == args["source_dt"] and d.get(args["join_field"]):
|
||||
args["name"] = d.get(args["join_field"])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%}
|
||||
{%- set align_class = resolve_class({
|
||||
'text-right': align == 'Right',
|
||||
'text-centre': align == 'Centre',
|
||||
'text-center': align == 'Centre',
|
||||
'text-left': align == 'Left',
|
||||
}) -%}
|
||||
|
||||
|
||||
@@ -322,8 +322,6 @@ erpnext.patches.v14_0.update_closing_balances #14-07-2023
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||
erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts
|
||||
erpnext.patches.v14_0.update_subscription_details
|
||||
# below migration patches should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
execute:frappe.delete_doc_if_exists("Report", "Tax Detail")
|
||||
erpnext.patches.v15_0.enable_all_leads
|
||||
erpnext.patches.v14_0.update_company_in_ldc
|
||||
@@ -340,3 +338,6 @@ erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
|
||||
execute:frappe.defaults.clear_default("fiscal_year")
|
||||
erpnext.patches.v15_0.remove_exotel_integration
|
||||
erpnext.patches.v14_0.single_to_multi_dunning
|
||||
execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
||||
@@ -98,9 +98,11 @@ def get_timesheets(filters):
|
||||
record_filters = [
|
||||
["start_date", "<=", filters.to_date],
|
||||
["end_date", ">=", filters.from_date],
|
||||
["docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if not filters.get("include_draft_timesheets"):
|
||||
record_filters.append(["docstatus", "=", 1])
|
||||
else:
|
||||
record_filters.append(["docstatus", "!=", 2])
|
||||
if "employee" in filters:
|
||||
record_filters.append(["employee", "=", filters.employee])
|
||||
|
||||
|
||||
@@ -25,5 +25,10 @@ frappe.query_reports["Employee Billing Summary"] = {
|
||||
default: frappe.datetime.add_days(frappe.datetime.month_start(), -1),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname:"include_draft_timesheets",
|
||||
label: __("Include Timesheets in Draft Status"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -25,5 +25,10 @@ frappe.query_reports["Project Billing Summary"] = {
|
||||
default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname:"include_draft_timesheets",
|
||||
label: __("Include Timesheets in Draft Status"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
name: __("Document Name"),
|
||||
editable: false,
|
||||
width: 1,
|
||||
format: (value, row) => {
|
||||
return frappe.form.formatters.Link(value, {options: row[2].content});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: __("Reference Date"),
|
||||
@@ -132,7 +135,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
format_row(row) {
|
||||
return [
|
||||
row[1], // Document Type
|
||||
frappe.form.formatters.Link(row[2], {options: row[1]}), // Document Name
|
||||
row[2], // Document Name
|
||||
row[5] || row[8], // Reference Date
|
||||
format_currency(row[3], row[9]), // Remaining
|
||||
row[4], // Reference Number
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"editable_price_list_rate",
|
||||
"validate_selling_price",
|
||||
"editable_bundle_item_rates",
|
||||
"allow_negative_rates_for_items",
|
||||
"sales_transactions_settings_section",
|
||||
"so_required",
|
||||
"dn_required",
|
||||
@@ -193,6 +194,12 @@
|
||||
"fieldname": "dont_reserve_sales_order_qty_on_sales_return",
|
||||
"fieldtype": "Check",
|
||||
"label": "Don't Reserve Sales Order Qty on Sales Return"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_negative_rates_for_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative rates for Items"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -200,7 +207,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-09 15:35:42.914354",
|
||||
"modified": "2023-08-14 20:33:05.693667",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
@@ -114,7 +114,7 @@ def create_transaction(doctype, company, start_date):
|
||||
if document_type == "Purchase Order":
|
||||
posting_date = get_random_date(start_date, 1, 30)
|
||||
else:
|
||||
posting_date = get_random_date(start_date, 31, 365)
|
||||
posting_date = get_random_date(start_date, 31, 364)
|
||||
|
||||
doctype.update(
|
||||
{
|
||||
@@ -180,8 +180,16 @@ def clear_masters():
|
||||
def clear_demo_record(document):
|
||||
document_type = document.get("doctype")
|
||||
del document["doctype"]
|
||||
doc = frappe.get_doc(document_type, document)
|
||||
frappe.delete_doc(doc.doctype, doc.name, ignore_permissions=True)
|
||||
|
||||
valid_columns = frappe.get_meta(document_type).get_valid_columns()
|
||||
|
||||
filters = document
|
||||
for key in list(filters):
|
||||
if key not in valid_columns:
|
||||
filters.pop(key, None)
|
||||
|
||||
doc = frappe.get_doc(document_type, filters)
|
||||
doc.delete(ignore_permissions=True)
|
||||
|
||||
|
||||
def delete_company(company):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Item Group",
|
||||
"item_group_name": "Demo Item Group",
|
||||
"parent_item_group": "All Item Groups"
|
||||
"item_group_name": "Demo Item Group"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"supplier": "Zuckerman Security Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -20,6 +21,7 @@
|
||||
"supplier": "MA Inc.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -36,6 +38,7 @@
|
||||
"supplier": "Summit Traders Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -52,6 +55,7 @@
|
||||
"supplier": "Zuckerman Security Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -68,6 +72,7 @@
|
||||
"supplier": "MA Inc.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -84,6 +89,7 @@
|
||||
"supplier": "Summit Traders Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -100,6 +106,7 @@
|
||||
"supplier": "Zuckerman Security Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -116,6 +123,7 @@
|
||||
"supplier": "MA Inc.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -132,6 +140,7 @@
|
||||
"supplier": "Summit Traders Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
@@ -148,6 +157,7 @@
|
||||
"supplier": "Zuckerman Security Ltd.",
|
||||
"doctype": "Purchase Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"customer": "Grant Plastics Ltd.",
|
||||
"doctype": "Sales Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Sales Order Item",
|
||||
@@ -20,6 +21,7 @@
|
||||
"customer": "West View Software Ltd.",
|
||||
"doctype": "Sales Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Sales Order Item",
|
||||
@@ -44,6 +46,7 @@
|
||||
"customer": "West View Software Ltd.",
|
||||
"doctype": "Sales Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Sales Order Item",
|
||||
@@ -76,6 +79,7 @@
|
||||
"customer": "Palmer Productions Ltd.",
|
||||
"doctype": "Sales Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Sales Order Item",
|
||||
@@ -92,6 +96,7 @@
|
||||
"customer": "Grant Plastics Ltd.",
|
||||
"doctype": "Sales Order",
|
||||
"update_stock": 1,
|
||||
"disable_rounded_total": 1,
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Sales Order Item",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{{ d.description }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
{{ doc.get_formatted("net_total") }}
|
||||
{{ d.get_formatted("base_tax_amount") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user