diff --git a/mo/util.py b/mo/util.py
index 790db555fc28e51c1fd33ea05670008d1d48f283..391097af852b5014aed763530dd1fd30e2e1e6ee 100644
--- a/mo/util.py
+++ b/mo/util.py
@@ -2,6 +2,7 @@
 
 from dataclasses import dataclass
 import datetime
+import decimal
 import dateutil.tz
 import email.message
 import email.headerregistry
@@ -12,13 +13,14 @@ import re
 import secrets
 import subprocess
 import sys
-from typing import Any, Optional, NoReturn
+from typing import Any, Optional, NoReturn, Tuple
 import textwrap
 import urllib.parse
 
 import mo
 import mo.db as db
 import mo.config as config
+from mo.util_format import format_decimal
 
 # Uživatel, který se uvádí jako pachatel v databázovém logu
 current_log_user: Optional[db.User] = None
@@ -209,3 +211,35 @@ def normalize_grade(grade: str) -> int:
             return -1
     except ValueError:
         return -1
+
+
+def parse_points(
+    raw_points: str, for_task: Optional[db.Task] = None, for_round: Optional[db.Round] = None,
+) -> Tuple[Optional[decimal.Decimal], Optional[str]]:
+    """Naparsuje a zkontroluje body. Vrátí body (jako decimal.Decimal nebo None
+    při prázdných bodech) a případný error (None pokud nenastal, jinak text chyby)."""
+    if raw_points == "":
+        return None, None
+    try:
+        points = decimal.Decimal(raw_points.replace(',', '.'))
+    except decimal.InvalidOperation:
+        return 0, f"Hodnota '{raw_points}' není číslo"
+
+    return points, check_points(points, for_task, for_round)
+
+
+def check_points(points: decimal.Decimal, for_task: Optional[db.Task] = None, for_round: Optional[db.Round] = None) -> Optional[str]:
+    """Zkontroluje body. Pokud je vše ok, tak vrátí None, jinak vrátí text chyby."""
+    if points < 0:
+        return f'Nelze zadat záporné body (zadáno {format_decimal(points)})'
+    if for_task and for_task.max_points is not None and points > for_task.max_points:
+        return f'Maximální počet bodů za úlohu je {format_decimal(for_task.max_points)}, nelze zadat více (zadáno {format_decimal(points)})'
+    if for_round and (points % for_round.master.points_step) != 0:
+        points_step = for_round.master.points_step
+        if points_step == 1:
+            return f'Podle nastavení kola lze zadat pouze celé body (hodnota {points} je neplatná)'
+        elif points_step == 0.5:
+            return f'Podle nastavení kola nelze zadat body s libovolnými desetinami, pouze půlbody (hodnota {points} je neplatná)'
+        else:
+            return f'Podle nastavení kola zadat body jen s krokem {points_step} (hodnota {points} je neplatná)'
+    return None