Add roll, Job Card and quotation-estimate fields; lay Makap fields out in tabs
- Batch gets roll details (stage, width, length, micron, net/gross kg, supplier lot, machine, Work Order) so each roll is a traceable batch (B3). - Job Card gets operator output and waste capture on its first tab, with a derived waste % and a required reason whenever waste is entered. - Quotation Item can cost a line from an estimating BOM: material grossed up for expected waste, BOM operating cost, set-up and cylinder charge, with the resulting margin shown against the quoted rate (M4). - About 120 fields used to stack after amended_from with no sections. They now sit in a "Makap" tab per form (a section on child rows) with labelled sub-sections, and positions are updated on migrate (m1). - Tooling Readiness is read-only: it is derived from the cylinder register. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,9 @@ doc_events = {
|
|||||||
"Work Order": {
|
"Work Order": {
|
||||||
"validate": "makap_backend.makap_backend.controls.validate_work_order",
|
"validate": "makap_backend.makap_backend.controls.validate_work_order",
|
||||||
},
|
},
|
||||||
|
"Job Card": {
|
||||||
|
"validate": "makap_backend.makap_backend.controls.validate_job_card",
|
||||||
|
},
|
||||||
"Quality Inspection": {
|
"Quality Inspection": {
|
||||||
"validate": "makap_backend.makap_backend.controls.validate_quality_inspection",
|
"validate": "makap_backend.makap_backend.controls.validate_quality_inspection",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.utils import flt
|
||||||
|
|
||||||
from makap_backend.makap_backend import work_order_readiness
|
from makap_backend.makap_backend import work_order_readiness
|
||||||
|
|
||||||
@@ -73,6 +74,60 @@ def validate_quotation(doc, method=None):
|
|||||||
line.makap_specification_revision = spec.revision
|
line.makap_specification_revision = spec.revision
|
||||||
line.makap_technical_summary = spec.customer_facing_technical_summary
|
line.makap_technical_summary = spec.customer_facing_technical_summary
|
||||||
|
|
||||||
|
_estimate_line_cost(line)
|
||||||
|
|
||||||
|
|
||||||
|
def _estimate_line_cost(line):
|
||||||
|
"""Cost a quotation line from its estimating BOM: material (grossed up for
|
||||||
|
expected waste) + conversion (BOM operations) + set-up + cylinder charge.
|
||||||
|
|
||||||
|
The rate stays the salesperson's decision; this only shows the cost and
|
||||||
|
margin behind it, in company currency.
|
||||||
|
"""
|
||||||
|
if not line.get("makap_estimate_bom"):
|
||||||
|
for field in ("makap_material_cost", "makap_conversion_cost", "makap_estimated_cost", "makap_estimated_margin_pct"):
|
||||||
|
line.set(field, 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
bom = frappe.db.get_value(
|
||||||
|
"BOM",
|
||||||
|
line.makap_estimate_bom,
|
||||||
|
["item", "quantity", "raw_material_cost", "operating_cost", "docstatus"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
if not bom or bom.docstatus == 2:
|
||||||
|
frappe.throw(f"Estimating BOM {line.makap_estimate_bom} does not exist or is cancelled.")
|
||||||
|
if bom.item != line.item_code:
|
||||||
|
frappe.throw(f"Estimating BOM {line.makap_estimate_bom} is for {bom.item}, not {line.item_code}.")
|
||||||
|
|
||||||
|
qty = flt(line.stock_qty or line.qty)
|
||||||
|
per_unit = 1 / flt(bom.quantity or 1)
|
||||||
|
waste_factor = 1 + flt(line.makap_waste_pct) / 100
|
||||||
|
|
||||||
|
line.makap_material_cost = flt(bom.raw_material_cost) * per_unit * qty * waste_factor
|
||||||
|
line.makap_conversion_cost = flt(bom.operating_cost) * per_unit * qty
|
||||||
|
line.makap_estimated_cost = (
|
||||||
|
line.makap_material_cost
|
||||||
|
+ line.makap_conversion_cost
|
||||||
|
+ flt(line.makap_setup_cost)
|
||||||
|
+ flt(line.makap_cylinder_charge)
|
||||||
|
)
|
||||||
|
revenue = flt(line.base_net_amount or line.base_amount)
|
||||||
|
line.makap_estimated_margin_pct = (revenue - line.makap_estimated_cost) / revenue * 100 if revenue else 0
|
||||||
|
|
||||||
|
|
||||||
|
def validate_job_card(doc, method=None):
|
||||||
|
setup = flt(doc.get("makap_setup_waste_kg"))
|
||||||
|
running = flt(doc.get("makap_running_waste_kg"))
|
||||||
|
output = flt(doc.get("makap_output_weight_kg"))
|
||||||
|
if setup < 0 or running < 0 or output < 0:
|
||||||
|
frappe.throw("Output and waste weights cannot be negative.")
|
||||||
|
|
||||||
|
waste = setup + running
|
||||||
|
doc.makap_waste_pct = waste / (output + waste) * 100 if (output + waste) else 0
|
||||||
|
if waste and not doc.get("makap_waste_reason"):
|
||||||
|
frappe.throw("Record the main waste reason when waste is entered.")
|
||||||
|
|
||||||
|
|
||||||
def validate_sales_order(doc, method=None):
|
def validate_sales_order(doc, method=None):
|
||||||
if doc.docstatus == 1 and not doc.makap_customer_acceptance_basis:
|
if doc.docstatus == 1 and not doc.makap_customer_acceptance_basis:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
|
||||||
|
|
||||||
FIELDS = [
|
FIELDS = [
|
||||||
@@ -13,6 +14,7 @@ FIELDS = [
|
|||||||
("Sales Order", "makap_acceptance_reference", "Acceptance Reference", "Data", None, 0),
|
("Sales Order", "makap_acceptance_reference", "Acceptance Reference", "Data", None, 0),
|
||||||
# Module 4 — manufacturing readiness and process classification
|
# Module 4 — manufacturing readiness and process classification
|
||||||
("Operation", "makap_process_type", "Makap Process Type", "Select", "Printing — Gravure\nPrinting — Flexographic\nLamination\nSlitting\nRewinding\nConversion / Bag Making\nFinishing\nOther", 0),
|
("Operation", "makap_process_type", "Makap Process Type", "Select", "Printing — Gravure\nPrinting — Flexographic\nLamination\nSlitting\nRewinding\nConversion / Bag Making\nFinishing\nOther", 0),
|
||||||
|
("Work Order", "makap_release_section", "Release Readiness", "Section Break", None, 0),
|
||||||
("Work Order", "makap_packaging_specification", "Packaging Specification", "Link", "Packaging Specification", 1),
|
("Work Order", "makap_packaging_specification", "Packaging Specification", "Link", "Packaging Specification", 1),
|
||||||
("Work Order", "makap_specification_revision", "Specification Revision", "Data", None, 1),
|
("Work Order", "makap_specification_revision", "Specification Revision", "Data", None, 1),
|
||||||
("Work Order", "makap_sales_order_item_reference", "Sales Order Item Reference", "Data", None, 0),
|
("Work Order", "makap_sales_order_item_reference", "Sales Order Item Reference", "Data", None, 0),
|
||||||
@@ -25,9 +27,10 @@ FIELDS = [
|
|||||||
("Work Order", "makap_bom_compatibility_confirmed_by", "BOM Compatibility Confirmed By", "Link", "User", 1),
|
("Work Order", "makap_bom_compatibility_confirmed_by", "BOM Compatibility Confirmed By", "Link", "User", 1),
|
||||||
("Work Order", "makap_bom_compatibility_confirmed_on", "BOM Compatibility Confirmed On", "Datetime", None, 1),
|
("Work Order", "makap_bom_compatibility_confirmed_on", "BOM Compatibility Confirmed On", "Datetime", None, 1),
|
||||||
("Work Order", "makap_process_compatibility_status", "Process Compatibility Status", "Select", "Not Checked\nCompatible\nIncompatible\nNot Applicable", 0),
|
("Work Order", "makap_process_compatibility_status", "Process Compatibility Status", "Select", "Not Checked\nCompatible\nIncompatible\nNot Applicable", 0),
|
||||||
("Work Order", "makap_tooling_readiness_status", "Tooling Readiness Status", "Select", "Not Checked\nReady\nBlocked\nNot Required", 0),
|
("Work Order", "makap_tooling_readiness_status", "Tooling Readiness Status", "Select", "Not Checked\nReady\nBlocked\nNot Required", 1),
|
||||||
# Module 5 — bounded material accountability controls
|
# Module 5 — bounded material accountability controls
|
||||||
("Item", "makap_material_control_class", "Material Control Class", "Select", "Standard\nControlled\nHigh Traceability", 0),
|
("Item", "makap_material_control_class", "Material Control Class", "Select", "Standard\nControlled\nHigh Traceability", 0),
|
||||||
|
("Work Order", "makap_material_section", "Material Accountability", "Section Break", None, 0),
|
||||||
("Work Order", "makap_material_availability_status", "Material Availability Status", "Select", "Not Checked\nBlocked\nPartially Available\nAvailable", 0),
|
("Work Order", "makap_material_availability_status", "Material Availability Status", "Select", "Not Checked\nBlocked\nPartially Available\nAvailable", 0),
|
||||||
("Work Order", "makap_material_availability_checked_by", "Material Availability Checked By", "Link", "User", 1),
|
("Work Order", "makap_material_availability_checked_by", "Material Availability Checked By", "Link", "User", 1),
|
||||||
("Work Order", "makap_material_availability_checked_on", "Material Availability Checked On", "Datetime", None, 1),
|
("Work Order", "makap_material_availability_checked_on", "Material Availability Checked On", "Datetime", None, 1),
|
||||||
@@ -42,6 +45,7 @@ FIELDS = [
|
|||||||
("Quality Inspection", "makap_work_order", "Work Order", "Link", "Work Order", 0),
|
("Quality Inspection", "makap_work_order", "Work Order", "Link", "Work Order", 0),
|
||||||
("Quality Inspection", "makap_quality_gate_stage", "Quality Gate Stage", "Select", "First-Off\nIn-Process\nFinal\nIncoming\nOutgoing\nNot Applicable", 0),
|
("Quality Inspection", "makap_quality_gate_stage", "Quality Gate Stage", "Select", "First-Off\nIn-Process\nFinal\nIncoming\nOutgoing\nNot Applicable", 0),
|
||||||
("Quality Inspection", "makap_inspection_trigger", "Inspection Trigger", "Select", "Routine\nFirst-Off\nPeriodic\nPost-Adjustment\nPost-Rework\nComplaint\nRetrospective Hold", 0),
|
("Quality Inspection", "makap_inspection_trigger", "Inspection Trigger", "Select", "Routine\nFirst-Off\nPeriodic\nPost-Adjustment\nPost-Rework\nComplaint\nRetrospective Hold", 0),
|
||||||
|
("Quality Inspection", "makap_eligibility_section", "Eligibility and Disposition", "Section Break", None, 0),
|
||||||
("Quality Inspection", "makap_quality_eligibility", "Quality Eligibility", "Select", "Not Determined\nEligible\nIneligible\nHold", 0),
|
("Quality Inspection", "makap_quality_eligibility", "Quality Eligibility", "Select", "Not Determined\nEligible\nIneligible\nHold", 0),
|
||||||
("Quality Inspection", "makap_inspection_population_quantity", "Inspection Population Quantity", "Float", None, 0),
|
("Quality Inspection", "makap_inspection_population_quantity", "Inspection Population Quantity", "Float", None, 0),
|
||||||
("Quality Inspection", "makap_population_uom", "Population UOM", "Link", "UOM", 0),
|
("Quality Inspection", "makap_population_uom", "Population UOM", "Link", "UOM", 0),
|
||||||
@@ -53,6 +57,7 @@ FIELDS = [
|
|||||||
("Quality Inspection", "makap_disposition_on", "Disposition On", "Datetime", None, 1),
|
("Quality Inspection", "makap_disposition_on", "Disposition On", "Datetime", None, 1),
|
||||||
("Quality Inspection", "makap_disposition_reason", "Disposition Reason", "Small Text", None, 0),
|
("Quality Inspection", "makap_disposition_reason", "Disposition Reason", "Small Text", None, 0),
|
||||||
("Quality Inspection", "makap_defect_failure_category", "Defect / Failure Category", "Data", None, 0),
|
("Quality Inspection", "makap_defect_failure_category", "Defect / Failure Category", "Data", None, 0),
|
||||||
|
("Quality Inspection", "makap_exception_section", "Exceptions, Reinspection and Concessions", "Section Break", None, 0),
|
||||||
("Quality Inspection", "makap_quality_exception_type", "Quality Exception Type", "Select", "None\nConcession\nReinspection\nRetrospective Hold\nTraceability Exception", 0),
|
("Quality Inspection", "makap_quality_exception_type", "Quality Exception Type", "Select", "None\nConcession\nReinspection\nRetrospective Hold\nTraceability Exception", 0),
|
||||||
("Quality Inspection", "makap_reinspection_of", "Reinspection Of", "Link", "Quality Inspection", 0),
|
("Quality Inspection", "makap_reinspection_of", "Reinspection Of", "Link", "Quality Inspection", 0),
|
||||||
("Quality Inspection", "makap_reinspection_type", "Reinspection Type", "Select", "Full\nPartial\nTargeted", 0),
|
("Quality Inspection", "makap_reinspection_type", "Reinspection Type", "Select", "Full\nPartial\nTargeted", 0),
|
||||||
@@ -84,6 +89,7 @@ FIELDS = [
|
|||||||
("Delivery Note", "makap_dispatch_accountability_notes", "Dispatch Accountability Notes", "Small Text", None, 0),
|
("Delivery Note", "makap_dispatch_accountability_notes", "Dispatch Accountability Notes", "Small Text", None, 0),
|
||||||
# Placeholder only — real deposit/credit logic lands in Module 8B. Defaults to Not Required
|
# Placeholder only — real deposit/credit logic lands in Module 8B. Defaults to Not Required
|
||||||
# so the Dispatch Gate can already evaluate it without blocking every delivery today.
|
# so the Dispatch Gate can already evaluate it without blocking every delivery today.
|
||||||
|
("Delivery Note", "makap_release_section", "Financial Release and Short-Close", "Section Break", None, 0),
|
||||||
("Delivery Note", "makap_financial_release_status", "Financial Release Status", "Select", "Not Required\nNot Assessed\nBlocked\nCleared\nException Approved", 0),
|
("Delivery Note", "makap_financial_release_status", "Financial Release Status", "Select", "Not Required\nNot Assessed\nBlocked\nCleared\nException Approved", 0),
|
||||||
("Delivery Note", "makap_short_close", "Short-Close", "Check", None, 0),
|
("Delivery Note", "makap_short_close", "Short-Close", "Check", None, 0),
|
||||||
("Delivery Note", "makap_short_close_reason", "Short-Close Reason", "Small Text", None, 0),
|
("Delivery Note", "makap_short_close_reason", "Short-Close Reason", "Small Text", None, 0),
|
||||||
@@ -130,32 +136,97 @@ FIELDS = [
|
|||||||
("Purchase Invoice Item", "makap_quantity_variance", "Quantity Variance", "Float", None, 1),
|
("Purchase Invoice Item", "makap_quantity_variance", "Quantity Variance", "Float", None, 1),
|
||||||
("Purchase Invoice Item", "makap_rate_variance", "Rate Variance", "Float", None, 1),
|
("Purchase Invoice Item", "makap_rate_variance", "Rate Variance", "Float", None, 1),
|
||||||
# Module 8F — manufacturing execution cost interpretation
|
# Module 8F — manufacturing execution cost interpretation
|
||||||
|
("Work Order", "makap_cost_section", "Cost Interpretation", "Section Break", None, 0),
|
||||||
("Work Order", "makap_cost_status", "Cost Status", "Select", "Not Assessed\nProvisional\nReady to Close\nClosed\nRestatement Required", 0),
|
("Work Order", "makap_cost_status", "Cost Status", "Select", "Not Assessed\nProvisional\nReady to Close\nClosed\nRestatement Required", 0),
|
||||||
("Work Order", "makap_cost_confidence", "Cost Confidence", "Select", "Not Assessed\nHigh\nQualified\nLow / Provisional", 0),
|
("Work Order", "makap_cost_confidence", "Cost Confidence", "Select", "Not Assessed\nHigh\nQualified\nLow / Provisional", 0),
|
||||||
("Work Order", "makap_cost_attribution_basis", "Cost Attribution Basis", "Select", "Not Assessed\nExact\nDirectly Derived\nPooled / Valuation-Based\nQualified", 0),
|
("Work Order", "makap_cost_attribution_basis", "Cost Attribution Basis", "Select", "Not Assessed\nExact\nDirectly Derived\nPooled / Valuation-Based\nQualified", 0),
|
||||||
|
# Roll tracking — every film/roll batch carries its physical dimensions (PRD 4, B3)
|
||||||
|
("Batch", "makap_roll_stage", "Roll Stage", "Select", "\nResin / Raw Material\nBase Film\nPrinted Roll\nLaminated Roll\nSlit Reel\nFinished Goods", 0),
|
||||||
|
("Batch", "makap_width_mm", "Width (mm)", "Float", None, 0),
|
||||||
|
("Batch", "makap_length_m", "Length (m)", "Float", None, 0),
|
||||||
|
("Batch", "makap_micron", "Thickness (micron)", "Float", None, 0),
|
||||||
|
("Batch", "makap_roll_col", None, "Column Break", None, 0),
|
||||||
|
("Batch", "makap_net_weight_kg", "Net Weight (kg)", "Float", None, 0),
|
||||||
|
("Batch", "makap_gross_weight_kg", "Gross Weight (kg)", "Float", None, 0),
|
||||||
|
("Batch", "makap_supplier_lot", "Supplier Lot / Roll No", "Data", None, 0),
|
||||||
|
("Batch", "makap_workstation", "Produced On", "Link", "Workstation", 0),
|
||||||
|
("Batch", "makap_work_order", "Work Order", "Link", "Work Order", 0),
|
||||||
|
# Operator capture on each stage's Job Card (PRD 4 stage 3, problem 4)
|
||||||
|
("Job Card", "makap_output_rolls", "Output Rolls", "Int", None, 0),
|
||||||
|
("Job Card", "makap_output_length_m", "Output Length (m)", "Float", None, 0),
|
||||||
|
("Job Card", "makap_output_weight_kg", "Output Weight (kg)", "Float", None, 0),
|
||||||
|
("Job Card", "makap_output_col", None, "Column Break", None, 0),
|
||||||
|
("Job Card", "makap_setup_waste_kg", "Set-up Waste (kg)", "Float", None, 0),
|
||||||
|
("Job Card", "makap_running_waste_kg", "Running Waste (kg)", "Float", None, 0),
|
||||||
|
("Job Card", "makap_waste_pct", "Waste %", "Percent", None, 1),
|
||||||
|
("Job Card", "makap_waste_reason", "Main Waste Reason", "Select", "\nSet-up / Make-ready\nRegistration\nColour Match\nWeb Break\nWrinkles / Creasing\nBond Failure\nSeal Failure\nEdge Trim\nMaterial Defect\nMachine Fault\nOther", 0),
|
||||||
|
("Job Card", "makap_waste_notes", "Waste Notes", "Small Text", None, 0),
|
||||||
|
# Quotation cost estimate from an estimating BOM (PRD 4 stage 1, M4)
|
||||||
|
("Quotation Item", "makap_estimate_bom", "Estimating BOM", "Link", "BOM", 0),
|
||||||
|
("Quotation Item", "makap_waste_pct", "Expected Waste %", "Percent", None, 0),
|
||||||
|
("Quotation Item", "makap_setup_cost", "Set-up Cost", "Currency", None, 0),
|
||||||
|
("Quotation Item", "makap_cylinder_charge", "Cylinder / Plate Charge", "Currency", None, 0),
|
||||||
|
("Quotation Item", "makap_estimate_col", None, "Column Break", None, 0),
|
||||||
|
("Quotation Item", "makap_material_cost", "Estimated Material Cost", "Currency", None, 1),
|
||||||
|
("Quotation Item", "makap_conversion_cost", "Estimated Conversion Cost", "Currency", None, 1),
|
||||||
|
("Quotation Item", "makap_estimated_cost", "Estimated Total Cost", "Currency", None, 1),
|
||||||
|
("Quotation Item", "makap_estimated_margin_pct", "Estimated Margin %", "Percent", None, 1),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def ensure_custom_fields():
|
# Where each DocType's Makap fields live. Parent forms get one "Makap" tab;
|
||||||
created = []
|
# child rows get a section. Operator and roll fields sit in the main view
|
||||||
|
# because they are filled in on the shop floor, not reviewed later.
|
||||||
|
CONTAINERS = {
|
||||||
|
"Job Card": ("Section Break", "Output and Waste", "actual_end_date"),
|
||||||
|
"Batch": ("Section Break", "Roll Details", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _end_anchor(meta):
|
||||||
|
"""Last standard field, keeping a trailing Connections tab last."""
|
||||||
|
standard = [df for df in meta.fields if not df.get("is_custom_field")]
|
||||||
|
if len(standard) > 1 and standard[-1].fieldtype == "Tab Break":
|
||||||
|
return standard[-2].fieldname
|
||||||
|
return standard[-1].fieldname
|
||||||
|
|
||||||
|
|
||||||
|
def build_custom_fields():
|
||||||
|
grouped = {}
|
||||||
for dt, fieldname, label, fieldtype, options, read_only in FIELDS:
|
for dt, fieldname, label, fieldtype, options, read_only in FIELDS:
|
||||||
|
grouped.setdefault(dt, []).append(
|
||||||
|
{"fieldname": fieldname, "label": label, "fieldtype": fieldtype, "options": options, "read_only": read_only}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for dt, fields in grouped.items():
|
||||||
if not frappe.db.exists("DocType", dt):
|
if not frappe.db.exists("DocType", dt):
|
||||||
continue
|
continue
|
||||||
if frappe.db.exists("Custom Field", {"dt": dt, "fieldname": fieldname}):
|
meta = frappe.get_meta(dt)
|
||||||
continue
|
if dt in CONTAINERS:
|
||||||
doc = frappe.get_doc(
|
break_type, label, anchor = CONTAINERS[dt]
|
||||||
|
elif meta.istable:
|
||||||
|
break_type, label, anchor = "Section Break", "Makap", None
|
||||||
|
else:
|
||||||
|
break_type, label, anchor = "Tab Break", "Makap", None
|
||||||
|
|
||||||
|
chain = [
|
||||||
{
|
{
|
||||||
"doctype": "Custom Field",
|
"fieldname": "makap_tab" if break_type == "Tab Break" else "makap_section",
|
||||||
"dt": dt,
|
|
||||||
"fieldname": fieldname,
|
|
||||||
"label": label,
|
"label": label,
|
||||||
"fieldtype": fieldtype,
|
"fieldtype": break_type,
|
||||||
"options": options,
|
"insert_after": anchor or _end_anchor(meta),
|
||||||
"read_only": read_only,
|
|
||||||
"insert_after": "amended_from" if dt in {"Sales Order", "Work Order", "Quality Inspection", "Delivery Note", "Sales Invoice", "Purchase Invoice"} else None,
|
|
||||||
}
|
}
|
||||||
)
|
]
|
||||||
doc.insert(ignore_permissions=True)
|
for field in fields:
|
||||||
created.append(f"{dt}.{fieldname}")
|
field["insert_after"] = chain[-1]["fieldname"]
|
||||||
frappe.db.commit()
|
chain.append(field)
|
||||||
return created
|
result[dt] = chain
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_custom_fields():
|
||||||
|
"""Create or update every Makap custom field, including its position on the form."""
|
||||||
|
fields = build_custom_fields()
|
||||||
|
create_custom_fields(fields, update=True)
|
||||||
|
return sorted(f"{dt}.{f['fieldname']}" for dt, rows in fields.items() for f in rows)
|
||||||
|
|||||||
Reference in New Issue
Block a user