mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-28 11:08:32 +00:00
fix: correct payment terms fetching and recalculation logic
This commit is contained in:
@@ -216,7 +216,7 @@
|
|||||||
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
||||||
"fieldname": "automatically_fetch_payment_terms",
|
"fieldname": "automatically_fetch_payment_terms",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Automatically Fetch Payment Terms from Order"
|
"label": "Automatically Fetch Payment Terms from Order/Quotation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||||
@@ -307,7 +307,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
|
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
||||||
"fieldname": "enable_common_party_accounting",
|
"fieldname": "enable_common_party_accounting",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Common Party Accounting"
|
"label": "Enable Common Party Accounting"
|
||||||
@@ -671,7 +671,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-12-26 19:46:55.093717",
|
"modified": "2026-03-06 14:49:11.467716",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
@@ -701,4 +701,4 @@
|
|||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -2502,13 +2502,14 @@ class AccountsController(TransactionBase):
|
|||||||
grand_total = self.get("rounded_total") or self.grand_total
|
grand_total = self.get("rounded_total") or self.grand_total
|
||||||
automatically_fetch_payment_terms = 0
|
automatically_fetch_payment_terms = 0
|
||||||
|
|
||||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
if self.doctype in ("Sales Invoice", "Purchase Invoice", "Sales Order"):
|
||||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
|
||||||
grand_total = grand_total - flt(self.write_off_amount)
|
|
||||||
po_or_so, doctype, fieldname = self.get_order_details()
|
po_or_so, doctype, fieldname = self.get_order_details()
|
||||||
automatically_fetch_payment_terms = cint(
|
automatically_fetch_payment_terms = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
||||||
)
|
)
|
||||||
|
if self.doctype != "Sales Order":
|
||||||
|
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||||
|
grand_total = grand_total - flt(self.write_off_amount)
|
||||||
|
|
||||||
if self.get("total_advance"):
|
if self.get("total_advance"):
|
||||||
if party_account_currency == self.company_currency:
|
if party_account_currency == self.company_currency:
|
||||||
@@ -2524,7 +2525,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
if not self.get("payment_schedule"):
|
if not self.get("payment_schedule"):
|
||||||
if (
|
if (
|
||||||
self.doctype in ["Sales Invoice", "Purchase Invoice"]
|
self.doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order"]
|
||||||
and automatically_fetch_payment_terms
|
and automatically_fetch_payment_terms
|
||||||
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
|
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
|
||||||
):
|
):
|
||||||
@@ -2579,17 +2580,21 @@ class AccountsController(TransactionBase):
|
|||||||
self.ignore_default_payment_terms_template = 1
|
self.ignore_default_payment_terms_template = 1
|
||||||
|
|
||||||
def get_order_details(self):
|
def get_order_details(self):
|
||||||
|
if not self.get("items"):
|
||||||
|
return None, None, None
|
||||||
if self.doctype == "Sales Invoice":
|
if self.doctype == "Sales Invoice":
|
||||||
po_or_so = self.get("items") and self.get("items")[0].get("sales_order")
|
prev_doc = self.get("items")[0].get("sales_order")
|
||||||
po_or_so_doctype = "Sales Order"
|
prev_doctype = "Sales Order"
|
||||||
po_or_so_doctype_name = "sales_order"
|
prev_doctype_name = "sales_order"
|
||||||
|
elif self.doctype == "Purchase Invoice":
|
||||||
|
prev_doc = self.get("items")[0].get("purchase_order")
|
||||||
|
prev_doctype = "Purchase Order"
|
||||||
|
prev_doctype_name = "purchase_order"
|
||||||
else:
|
else:
|
||||||
po_or_so = self.get("items") and self.get("items")[0].get("purchase_order")
|
prev_doc = self.get("items")[0].get("prevdoc_docname")
|
||||||
po_or_so_doctype = "Purchase Order"
|
prev_doctype = "Quotation"
|
||||||
po_or_so_doctype_name = "purchase_order"
|
prev_doctype_name = "prevdoc_docname"
|
||||||
|
return prev_doc, prev_doctype, prev_doctype_name
|
||||||
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
|
|
||||||
|
|
||||||
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
|
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
|
||||||
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
|
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
|
||||||
|
|||||||
@@ -1052,6 +1052,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
if (this.frm.doc.transaction_date) {
|
if (this.frm.doc.transaction_date) {
|
||||||
this.frm.transaction_date = this.frm.doc.transaction_date;
|
this.frm.transaction_date = this.frm.doc.transaction_date;
|
||||||
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
|
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
|
||||||
|
this.recalculate_terms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import flt, getdate, nowdate
|
from frappe.utils import cint, flt, getdate, nowdate
|
||||||
|
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
|
|
||||||
@@ -441,6 +441,11 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
|||||||
filtered_items = args.get("filtered_children", [])
|
filtered_items = args.get("filtered_children", [])
|
||||||
child_filter = d.name in filtered_items if filtered_items else True
|
child_filter = d.name in filtered_items if filtered_items else True
|
||||||
return child_filter
|
return child_filter
|
||||||
|
|
||||||
|
automatically_fetch_payment_terms = cint(
|
||||||
|
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
doclist = get_mapped_doc(
|
||||||
"Quotation",
|
"Quotation",
|
||||||
@@ -449,6 +454,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
|||||||
"Quotation": {
|
"Quotation": {
|
||||||
"doctype": "Sales Order",
|
"doctype": "Sales Order",
|
||||||
"validation": {"docstatus": ["=", 1]},
|
"validation": {"docstatus": ["=", 1]},
|
||||||
|
"field_no_map": ["payment_terms_template"],
|
||||||
},
|
},
|
||||||
"Quotation Item": {
|
"Quotation Item": {
|
||||||
"doctype": "Sales Order Item",
|
"doctype": "Sales Order Item",
|
||||||
@@ -465,6 +471,9 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
|||||||
ignore_permissions=ignore_permissions,
|
ignore_permissions=ignore_permissions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if automatically_fetch_payment_terms:
|
||||||
|
doclist.set_payment_schedule()
|
||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,11 @@ class TestQuotation(FrappeTestCase):
|
|||||||
quotation.insert()
|
quotation.insert()
|
||||||
|
|
||||||
self.assertTrue(quotation.payment_schedule)
|
self.assertTrue(quotation.payment_schedule)
|
||||||
|
|
||||||
|
@change_settings(
|
||||||
|
"Accounts Settings",
|
||||||
|
{"automatically_fetch_payment_terms": 1},
|
||||||
|
)
|
||||||
|
|
||||||
def test_make_sales_order_terms_copied(self):
|
def test_make_sales_order_terms_copied(self):
|
||||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||||
@@ -317,7 +322,7 @@ class TestQuotation(FrappeTestCase):
|
|||||||
|
|
||||||
@change_settings(
|
@change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 0},
|
{"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 0,"automatically_fetch_payment_terms": 1,},
|
||||||
)
|
)
|
||||||
def test_make_sales_order_with_terms(self):
|
def test_make_sales_order_with_terms(self):
|
||||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||||
@@ -355,10 +360,13 @@ class TestQuotation(FrappeTestCase):
|
|||||||
sales_order.save()
|
sales_order.save()
|
||||||
|
|
||||||
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00)
|
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00)
|
||||||
self.assertEqual(sales_order.payment_schedule[0].due_date, getdate(quotation.transaction_date))
|
self.assertEqual(
|
||||||
|
getdate(sales_order.payment_schedule[0].due_date), getdate(quotation.transaction_date)
|
||||||
|
)
|
||||||
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 8906.00)
|
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 8906.00)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
|
getdate(sales_order.payment_schedule[1].due_date),
|
||||||
|
getdate(add_days(quotation.transaction_date, 30)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_valid_till_before_transaction_date(self):
|
def test_valid_till_before_transaction_date(self):
|
||||||
@@ -1057,6 +1065,57 @@ class TestQuotation(FrappeTestCase):
|
|||||||
|
|
||||||
quotation.reload()
|
quotation.reload()
|
||||||
self.assertEqual(quotation.status, "Open")
|
self.assertEqual(quotation.status, "Open")
|
||||||
|
|
||||||
|
@change_settings(
|
||||||
|
"Accounts Settings",
|
||||||
|
{"automatically_fetch_payment_terms": 1},
|
||||||
|
)
|
||||||
|
def test_make_sales_order_with_payment_terms(self):
|
||||||
|
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||||
|
|
||||||
|
template = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template",
|
||||||
|
"template_name": "_Test Payment Terms Template for Quotation",
|
||||||
|
"terms": [
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"invoice_portion": 50.00,
|
||||||
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
|
"credit_days": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"invoice_portion": 50.00,
|
||||||
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
|
"credit_days": 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).save()
|
||||||
|
|
||||||
|
quotation = make_quotation(qty=10, rate=1000, do_not_submit=1)
|
||||||
|
quotation.transaction_date = add_days(nowdate(), -2)
|
||||||
|
quotation.valid_till = add_days(nowdate(), 10)
|
||||||
|
quotation.update({"payment_terms_template": template.name, "payment_schedule": []})
|
||||||
|
quotation.save()
|
||||||
|
quotation.submit()
|
||||||
|
|
||||||
|
self.assertEqual(quotation.payment_schedule[0].payment_amount, 5000)
|
||||||
|
self.assertEqual(quotation.payment_schedule[1].payment_amount, 5000)
|
||||||
|
self.assertEqual(quotation.payment_schedule[0].due_date, quotation.transaction_date)
|
||||||
|
self.assertEqual(quotation.payment_schedule[1].due_date, add_days(quotation.transaction_date, 10))
|
||||||
|
|
||||||
|
sales_order = make_sales_order(quotation.name)
|
||||||
|
sales_order.transaction_date = nowdate()
|
||||||
|
sales_order.delivery_date = nowdate()
|
||||||
|
sales_order.save()
|
||||||
|
|
||||||
|
self.assertEqual(sales_order.payment_schedule[0].due_date, sales_order.transaction_date)
|
||||||
|
self.assertEqual(sales_order.payment_schedule[1].due_date, add_days(sales_order.transaction_date, 10))
|
||||||
|
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 5000)
|
||||||
|
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 5000)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test_records = frappe.get_test_records("Quotation")
|
test_records = frappe.get_test_records("Quotation")
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
"language",
|
"language",
|
||||||
"additional_info_section",
|
"additional_info_section",
|
||||||
"is_internal_customer",
|
"is_internal_customer",
|
||||||
|
"ignore_default_payment_terms_template",
|
||||||
"represents_company",
|
"represents_company",
|
||||||
"column_break_152",
|
"column_break_152",
|
||||||
"source",
|
"source",
|
||||||
@@ -1484,9 +1485,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:doc.order_type == 'Maintenance';",
|
|
||||||
"fieldname": "skip_delivery_note",
|
"fieldname": "skip_delivery_note",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Skip Delivery Note",
|
"label": "Skip Delivery Note",
|
||||||
@@ -1665,13 +1666,22 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"is_virtual": 1,
|
"is_virtual": 1,
|
||||||
"label": "Last Scanned Warehouse"
|
"label": "Last Scanned Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "customer.is_internal_customer",
|
||||||
|
"fieldname": "ignore_default_payment_terms_template",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Ignore Default Payment Terms Template",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-02-06 11:06:16.092658",
|
"modified": "2026-03-06 15:03:35.717402",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order",
|
"name": "Sales Order",
|
||||||
@@ -1750,4 +1760,4 @@
|
|||||||
"title_field": "customer_name",
|
"title_field": "customer_name",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
||||||
@@ -52,21 +52,17 @@ class SalesOrder(SellingController):
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_schedule.payment_schedule import PaymentSchedule
|
from erpnext.accounts.doctype.payment_schedule.payment_schedule import PaymentSchedule
|
||||||
from erpnext.accounts.doctype.pricing_rule_detail.pricing_rule_detail import PricingRuleDetail
|
from erpnext.accounts.doctype.pricing_rule_detail.pricing_rule_detail import PricingRuleDetail
|
||||||
from erpnext.accounts.doctype.sales_taxes_and_charges.sales_taxes_and_charges import (
|
from erpnext.accounts.doctype.sales_taxes_and_charges.sales_taxes_and_charges import SalesTaxesandCharges
|
||||||
SalesTaxesandCharges,
|
|
||||||
)
|
|
||||||
from erpnext.selling.doctype.sales_order_item.sales_order_item import SalesOrderItem
|
from erpnext.selling.doctype.sales_order_item.sales_order_item import SalesOrderItem
|
||||||
from erpnext.selling.doctype.sales_team.sales_team import SalesTeam
|
from erpnext.selling.doctype.sales_team.sales_team import SalesTeam
|
||||||
from erpnext.stock.doctype.packed_item.packed_item import PackedItem
|
from erpnext.stock.doctype.packed_item.packed_item import PackedItem
|
||||||
|
from frappe.types import DF
|
||||||
|
|
||||||
additional_discount_percentage: DF.Float
|
additional_discount_percentage: DF.Float
|
||||||
address_display: DF.SmallText | None
|
address_display: DF.SmallText | None
|
||||||
advance_paid: DF.Currency
|
advance_paid: DF.Currency
|
||||||
advance_payment_status: DF.Literal["Not Requested", "Requested", "Partially Paid", "Fully Paid"]
|
|
||||||
amended_from: DF.Link | None
|
amended_from: DF.Link | None
|
||||||
amount_eligible_for_commission: DF.Currency
|
amount_eligible_for_commission: DF.Currency
|
||||||
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
|
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
|
||||||
@@ -100,9 +96,7 @@ class SalesOrder(SellingController):
|
|||||||
customer_group: DF.Link | None
|
customer_group: DF.Link | None
|
||||||
customer_name: DF.Data | None
|
customer_name: DF.Data | None
|
||||||
delivery_date: DF.Date | None
|
delivery_date: DF.Date | None
|
||||||
delivery_status: DF.Literal[
|
delivery_status: DF.Literal["Not Delivered", "Fully Delivered", "Partly Delivered", "Closed", "Not Applicable"]
|
||||||
"Not Delivered", "Fully Delivered", "Partly Delivered", "Closed", "Not Applicable"
|
|
||||||
]
|
|
||||||
disable_rounded_total: DF.Check
|
disable_rounded_total: DF.Check
|
||||||
discount_amount: DF.Currency
|
discount_amount: DF.Currency
|
||||||
dispatch_address: DF.SmallText | None
|
dispatch_address: DF.SmallText | None
|
||||||
@@ -111,6 +105,7 @@ class SalesOrder(SellingController):
|
|||||||
grand_total: DF.Currency
|
grand_total: DF.Currency
|
||||||
group_same_items: DF.Check
|
group_same_items: DF.Check
|
||||||
has_unit_price_items: DF.Check
|
has_unit_price_items: DF.Check
|
||||||
|
ignore_default_payment_terms_template: DF.Check
|
||||||
ignore_pricing_rule: DF.Check
|
ignore_pricing_rule: DF.Check
|
||||||
in_words: DF.Data | None
|
in_words: DF.Data | None
|
||||||
incoterm: DF.Link | None
|
incoterm: DF.Link | None
|
||||||
@@ -154,18 +149,7 @@ class SalesOrder(SellingController):
|
|||||||
shipping_rule: DF.Link | None
|
shipping_rule: DF.Link | None
|
||||||
skip_delivery_note: DF.Check
|
skip_delivery_note: DF.Check
|
||||||
source: DF.Link | None
|
source: DF.Link | None
|
||||||
status: DF.Literal[
|
status: DF.Literal["", "Draft", "On Hold", "To Deliver and Bill", "To Bill", "To Deliver", "Completed", "Cancelled", "Closed"]
|
||||||
"",
|
|
||||||
"Draft",
|
|
||||||
"On Hold",
|
|
||||||
"To Pay",
|
|
||||||
"To Deliver and Bill",
|
|
||||||
"To Bill",
|
|
||||||
"To Deliver",
|
|
||||||
"Completed",
|
|
||||||
"Cancelled",
|
|
||||||
"Closed",
|
|
||||||
]
|
|
||||||
tax_category: DF.Link | None
|
tax_category: DF.Link | None
|
||||||
tax_id: DF.Data | None
|
tax_id: DF.Data | None
|
||||||
taxes: DF.Table[SalesTaxesandCharges]
|
taxes: DF.Table[SalesTaxesandCharges]
|
||||||
|
|||||||
Reference in New Issue
Block a user