Merge branch 'develop' into improve_taxes_setup
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index d8aae66..09666c5 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -13,6 +13,7 @@
"stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim",
"leave_settings",
+ "send_leave_notification",
"leave_approval_notification_template",
"leave_status_notification_template",
"role_allowed_to_create_backdated_leave_application",
@@ -69,15 +70,19 @@
"label": "Leave Settings"
},
{
+ "depends_on": "eval: doc.send_leave_notification == 1",
"fieldname": "leave_approval_notification_template",
"fieldtype": "Link",
"label": "Leave Approval Notification Template",
+ "mandatory_depends_on": "eval: doc.send_leave_notification == 1",
"options": "Email Template"
},
{
+ "depends_on": "eval: doc.send_leave_notification == 1",
"fieldname": "leave_status_notification_template",
"fieldtype": "Link",
"label": "Leave Status Notification Template",
+ "mandatory_depends_on": "eval: doc.send_leave_notification == 1",
"options": "Email Template"
},
{
@@ -132,13 +137,19 @@
"fieldname": "automatically_allocate_leaves_based_on_leave_policy",
"fieldtype": "Check",
"label": "Automatically Allocate Leaves Based On Leave Policy"
+ },
+ {
+ "default": "1",
+ "fieldname": "send_leave_notification",
+ "fieldtype": "Check",
+ "label": "Send Leave Notification"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2021-02-25 12:31:14.947865",
+ "modified": "2021-03-14 02:04:22.907159",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 132c3bd..350cead 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -40,7 +40,8 @@
def on_update(self):
if self.status == "Open" and self.docstatus < 1:
# notify leave approver about creation
- self.notify_leave_approver()
+ if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
+ self.notify_leave_approver()
def on_submit(self):
if self.status == "Open":
@@ -50,7 +51,8 @@
self.update_attendance()
# notify leave applier about approval
- self.notify_employee()
+ if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
+ self.notify_employee()
self.create_leave_ledger_entry()
self.reload()
@@ -60,7 +62,8 @@
def on_cancel(self):
self.create_leave_ledger_entry(submit=False)
# notify leave applier about cancellation
- self.notify_employee()
+ if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
+ self.notify_employee()
self.cancel_attendance()
def validate_applicable_after(self):
diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py
index e947588..6a2a06d 100644
--- a/erpnext/non_profit/doctype/donation/donation.py
+++ b/erpnext/non_profit/doctype/donation/donation.py
@@ -91,6 +91,10 @@
if not data.event == 'payment.captured':
return
+ # to avoid capturing subscription payments as donations
+ if payment.description and 'subscription' in str(payment.description).lower():
+ return
+
donor = get_donor(payment.email)
if not donor:
donor = create_donor(payment)
@@ -119,7 +123,7 @@
'donor_name': donor.donor_name,
'email': donor.email,
'date': getdate(),
- 'amount': flt(payment.amount),
+ 'amount': flt(payment.amount) / 100, # Convert to rupees from paise
'mode_of_payment': payment.method,
'razorpay_payment_id': payment.id
}).insert(ignore_mandatory=True)
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 191281f..c41a2f5 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -48,7 +48,7 @@
last_membership = erpnext.get_last_membership(self.member)
# if person applied for offline membership
- if last_membership and not frappe.session.user == "Administrator":
+ if last_membership and last_membership != self.name and not frappe.session.user == "Administrator":
# if last membership does not expire in 30 days, then do not allow to renew
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
frappe.throw(_("You can only renew if your membership expires within 30 days"))
@@ -287,7 +287,7 @@
membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
except Exception as e:
- message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
+ message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log)
return { "status": "Failed", "reason": e}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 59b12f3..7016ecd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -759,3 +759,4 @@
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
+execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
diff --git a/erpnext/patches/v12_0/add_state_code_for_ladakh.py b/erpnext/patches/v12_0/add_state_code_for_ladakh.py
index d41101c..29a7b4b 100644
--- a/erpnext/patches/v12_0/add_state_code_for_ladakh.py
+++ b/erpnext/patches/v12_0/add_state_code_for_ladakh.py
@@ -11,6 +11,7 @@
# Update options in gst_state custom fields
for field in custom_fields:
- gst_state_field = frappe.get_doc('Custom Field', field)
- gst_state_field.options = '\n'.join(states)
- gst_state_field.save()
+ if frappe.db.exists('Custom Field', field):
+ gst_state_field = frappe.get_doc('Custom Field', field)
+ gst_state_field.options = '\n'.join(states)
+ gst_state_field.save()
diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
index aea53f8..833c355 100644
--- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
+++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
@@ -2,15 +2,12 @@
from erpnext.regional.india.setup import make_custom_fields
def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company:
- return
+ if frappe.get_all('Company', filters = {'country': 'India'}):
+ make_custom_fields()
- make_custom_fields()
-
- if not frappe.db.exists('Party Type', 'Donor'):
- frappe.get_doc({
- 'doctype': 'Party Type',
- 'party_type': 'Donor',
- 'account_type': 'Receivable'
- }).insert(ignore_permissions=True)
\ No newline at end of file
+ if not frappe.db.exists('Party Type', 'Donor'):
+ frappe.get_doc({
+ 'doctype': 'Party Type',
+ 'party_type': 'Donor',
+ 'account_type': 'Receivable'
+ }).insert(ignore_permissions=True)
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index f5af677..029e11f 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -89,10 +89,11 @@
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
return amount_per_day * no_of_days
-@frappe.whitelist()
-def get_additional_salary_component(employee, start_date, end_date, component_type):
- additional_salaries = frappe.db.sql("""
- select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
+def get_additional_salaries(employee, start_date, end_date, component_type):
+ additional_salary_list = frappe.db.sql("""
+ select name, salary_component as component, type, amount,
+ overwrite_salary_structure_amount as overwrite,
+ deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary`
where employee=%(employee)s
and docstatus = 1
@@ -102,7 +103,7 @@
from_date <= %(to_date)s and to_date >= %(to_date)s
)
and type = %(component_type)s
- order by salary_component, overwrite_salary_structure_amount DESC
+ order by salary_component, overwrite ASC
""", {
'employee': employee,
'from_date': start_date,
@@ -110,38 +111,18 @@
'component_type': "Earning" if component_type == "earnings" else "Deduction"
}, as_dict=1)
- existing_salary_components= []
- salary_components_details = {}
- additional_salary_details = []
+ additional_salaries = []
+ components_to_overwrite = []
- overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1]
+ for d in additional_salary_list:
+ if d.overwrite:
+ if d.component in components_to_overwrite:
+ frappe.throw(_("Multiple Additional Salaries with overwrite "
+ "property exist for Salary Component {0} between {1} and {2}.").format(
+ frappe.bold(d.component), start_date, end_date), title=_("Error"))
- component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
- for d in additional_salaries:
+ components_to_overwrite.append(d.component)
- if d.salary_component not in existing_salary_components:
- component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
- struct_row = frappe._dict({'salary_component': d.salary_component})
- if component:
- struct_row.update(component[0])
+ additional_salaries.append(d)
- struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
- struct_row['is_additional_component'] = 1
-
- salary_components_details[d.salary_component] = struct_row
-
-
- if overwrites_components.count(d.salary_component) > 1:
- frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error"))
- else:
- additional_salary_details.append({
- 'name': d.name,
- 'component': d.salary_component,
- 'amount': d.amount,
- 'type': d.type,
- 'overwrite': d.overwrite_salary_structure_amount,
- })
-
- existing_salary_components.append(d.salary_component)
-
- return salary_components_details, additional_salary_details
+ return additional_salaries
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 595d697..a04a635 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -13,7 +13,7 @@
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase
from frappe.utils.background_jobs import enqueue
-from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salary_component
+from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
@@ -524,7 +524,7 @@
except NameError as err:
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
- title=_("Name error"))
+ title=_("Name error"))
except SyntaxError as err:
frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
except Exception as e:
@@ -558,15 +558,16 @@
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
def add_additional_salary_components(self, component_type):
- salary_components_details, additional_salary_details = get_additional_salary_component(self.employee,
+ additional_salaries = get_additional_salaries(self.employee,
self.start_date, self.end_date, component_type)
- if salary_components_details and additional_salary_details:
- for additional_salary in additional_salary_details:
- additional_salary =frappe._dict(additional_salary)
- amount = additional_salary.amount
- overwrite = additional_salary.overwrite
- self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount,
- component_type, overwrite=overwrite, additional_salary=additional_salary.name)
+
+ for additional_salary in additional_salaries:
+ self.update_component_row(
+ get_salary_component_data(additional_salary.component),
+ additional_salary.amount,
+ component_type,
+ additional_salary
+ )
def add_tax_components(self, payroll_period):
# Calculate variable_based_on_taxable_salary after all components updated in salary slip
@@ -583,47 +584,59 @@
for d in tax_components:
tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period)
- tax_row = self.get_salary_slip_row(d)
+ tax_row = get_salary_component_data(d)
self.update_component_row(tax_row, tax_amount, "deductions")
- def update_component_row(self, struct_row, amount, key, overwrite=1, additional_salary = ''):
+ def update_component_row(self, component_data, amount, component_type, additional_salary=None):
component_row = None
- for d in self.get(key):
- if d.salary_component == struct_row.salary_component:
+ for d in self.get(component_type):
+ if d.salary_component != component_data.salary_component:
+ continue
+
+ if (
+ not d.additional_salary
+ and (not additional_salary or additional_salary.overwrite)
+ or additional_salary
+ and additional_salary.name == d.additional_salary
+ ):
component_row = d
+ break
- if not component_row or (struct_row.get("is_additional_component") and not overwrite):
- if amount:
- self.append(key, {
- 'amount': amount,
- 'default_amount': amount if not struct_row.get("is_additional_component") else 0,
- 'depends_on_payment_days' : struct_row.depends_on_payment_days,
- 'salary_component' : struct_row.salary_component,
- 'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"),
- 'additional_salary': additional_salary,
- 'do_not_include_in_total' : struct_row.do_not_include_in_total,
- 'is_tax_applicable': struct_row.is_tax_applicable,
- 'is_flexible_benefit': struct_row.is_flexible_benefit,
- 'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary,
- 'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date,
- 'additional_amount': amount if struct_row.get("is_additional_component") else 0,
- 'exempted_from_income_tax': struct_row.exempted_from_income_tax
- })
+ if additional_salary and additional_salary.overwrite:
+ # Additional Salary with overwrite checked, remove default rows of same component
+ self.set(component_type, [
+ d for d in self.get(component_type)
+ if d.salary_component != component_data.salary_component
+ or d.additional_salary and additional_salary.name != d.additional_salary
+ or d == component_row
+ ])
+
+ if not component_row:
+ if not amount:
+ return
+
+ component_row = self.append(component_type)
+ for attr in (
+ 'depends_on_payment_days', 'salary_component', 'abbr'
+ 'do_not_include_in_total', 'is_tax_applicable',
+ 'is_flexible_benefit', 'variable_based_on_taxable_salary',
+ 'exempted_from_income_tax'
+ ):
+ component_row.set(attr, component_data.get(attr))
+
+ if additional_salary:
+ component_row.default_amount = 0
+ component_row.additional_amount = amount
+ component_row.additional_salary = additional_salary.name
+ component_row.deduct_full_tax_on_selected_payroll_date = \
+ additional_salary.deduct_full_tax_on_selected_payroll_date
else:
- if struct_row.get("is_additional_component"):
- if overwrite:
- component_row.additional_amount = amount - component_row.get("default_amount", 0)
- component_row.additional_salary = additional_salary
- else:
- component_row.additional_amount = amount
+ component_row.default_amount = amount
+ component_row.additional_amount = 0
+ component_row.deduct_full_tax_on_selected_payroll_date = \
+ component_data.deduct_full_tax_on_selected_payroll_date
- if not overwrite and component_row.default_amount:
- amount += component_row.default_amount
- else:
- component_row.default_amount = amount
-
- component_row.amount = amount
- component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date
+ component_row.amount = amount
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
if not payroll_period:
@@ -950,26 +963,13 @@
return frappe.safe_eval(condition, self.whitelisted_globals, data)
except NameError as err:
frappe.throw(_("{0} <br> This error can be due to missing or deleted field.").format(err),
- title=_("Name error"))
+ title=_("Name error"))
except SyntaxError as err:
frappe.throw(_("Syntax error in condition: {0}").format(err))
except Exception as e:
frappe.throw(_("Error in formula or condition: {0}").format(e))
raise
- def get_salary_slip_row(self, salary_component):
- component = frappe.get_doc("Salary Component", salary_component)
- # Data for update_component_row
- struct_row = frappe._dict()
- struct_row['depends_on_payment_days'] = component.depends_on_payment_days
- struct_row['salary_component'] = component.name
- struct_row['abbr'] = component.salary_component_abbr
- struct_row['do_not_include_in_total'] = component.do_not_include_in_total
- struct_row['is_tax_applicable'] = component.is_tax_applicable
- struct_row['is_flexible_benefit'] = component.is_flexible_benefit
- struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
- return struct_row
-
def get_component_totals(self, component_type, depends_on_payment_days=0):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
@@ -1032,7 +1032,6 @@
self.total_loan_repayment += payment.total_payment
def get_loan_details(self):
-
return frappe.get_all("Loan",
fields=["name", "interest_income_account", "loan_account", "loan_type"],
filters = {
@@ -1263,3 +1262,19 @@
def generate_password_for_pdf(policy_template, employee):
employee = frappe.get_doc("Employee", employee)
return policy_template.format(**employee.as_dict())
+
+def get_salary_component_data(component):
+ return frappe.get_value(
+ "Salary Component",
+ component,
+ [
+ "name as salary_component",
+ "depends_on_payment_days",
+ "salary_component_abbr as abbr",
+ "do_not_include_in_total",
+ "is_tax_applicable",
+ "is_flexible_benefit",
+ "variable_based_on_taxable_salary",
+ ],
+ as_dict=1,
+ )
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
index d734a18..ef384d4 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
+++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
@@ -29,7 +29,10 @@
def validate_duplicates(self):
if self.recipient == 'Donor':
- certificate = frappe.db.exists(self.doctype, {'donation': self.donation})
+ certificate = frappe.db.exists(self.doctype, {
+ 'donation': self.donation,
+ 'name': ('!=', self.name)
+ })
if certificate:
frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format(
get_link_to_form(self.doctype, certificate), frappe.bold(self.donation)
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 1a618d6..e24bd6c 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -719,25 +719,12 @@
if country != 'India':
return
- if not doc.total_taxes_and_charges:
+ gst_tax, base_gst_tax = get_gst_tax_amount(doc)
+
+ if not base_gst_tax:
return
if doc.reverse_charge == 'Y':
- gst_accounts = get_gst_accounts(doc.company)
- gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
- + gst_accounts.get('igst_account')
-
- base_gst_tax = 0
- gst_tax = 0
-
- for tax in doc.get('taxes'):
- if tax.category not in ("Total", "Valuation and Total"):
- continue
-
- if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
- base_gst_tax += tax.base_tax_amount_after_discount_amount
- gst_tax += tax.tax_amount_after_discount_amount
-
doc.taxes_and_charges_added -= gst_tax
doc.total_taxes_and_charges -= gst_tax
doc.base_taxes_and_charges_added -= base_gst_tax
@@ -771,6 +758,11 @@
if country != 'India':
return gl_entries
+ gst_tax, base_gst_tax = get_gst_tax_amount(doc)
+
+ if not base_gst_tax:
+ return gl_entries
+
if doc.reverse_charge == 'Y':
gst_accounts = get_gst_accounts(doc.company)
gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
@@ -799,6 +791,24 @@
return gl_entries
+def get_gst_tax_amount(doc):
+ gst_accounts = get_gst_accounts(doc.company)
+ gst_account_list = gst_accounts.get('cgst_account', []) + gst_accounts.get('sgst_account', []) \
+ + gst_accounts.get('igst_account', [])
+
+ base_gst_tax = 0
+ gst_tax = 0
+
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
+ base_gst_tax += tax.base_tax_amount_after_discount_amount
+ gst_tax += tax.tax_amount_after_discount_amount
+
+ return gst_tax, base_gst_tax
+
@frappe.whitelist()
def get_regional_round_off_accounts(company, account_list):
country = frappe.get_cached_value('Company', company, 'country')
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 0bb480b..1e424dd 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -161,5 +161,4 @@
navbar_settings.save()
def add_app_name():
- settings = frappe.get_doc("System Settings")
- settings.app_name = _("ERPNext")
\ No newline at end of file
+ frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 5539123..2079cf8 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -717,6 +717,18 @@
.on('focus', function(e) {
$(e.target).val('').trigger('input');
})
+ .on("awesomplete-open", () => {
+ let modal = field.$input.parents('.modal-dialog')[0];
+ if (modal) {
+ $(modal).removeClass("modal-dialog-scrollable");
+ }
+ })
+ .on("awesomplete-close", () => {
+ let modal = field.$input.parents('.modal-dialog')[0];
+ if (modal) {
+ $(modal).addClass("modal-dialog-scrollable");
+ }
+ });
});
},
diff --git a/erpnext/templates/includes/issue_row.html b/erpnext/templates/includes/issue_row.html
index d909c5f..a04f558 100644
--- a/erpnext/templates/includes/issue_row.html
+++ b/erpnext/templates/includes/issue_row.html
@@ -1,6 +1,6 @@
<div class="web-list-item transaction-list-item">
<a href="/issues?name={{ doc.name }}" class="no-underline">
- <div class="row py-4 border-bottom">
+ <div class="row py-4">
<div class="col-3 d-flex align-items-center">
{% set indicator = 'red' if doc.status == 'Open' else 'gray' %}
{% set indicator = 'green' if doc.status == 'Closed' else indicator %}