Merge pull request #36656 from frappe/develop

chore: release v15 beta
This commit is contained in:
Ankush Menat
2023-08-15 15:45:58 +05:30
committed by GitHub
23 changed files with 346 additions and 136 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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