diff --git a/db/upgrade-20210906.sql b/db/upgrade-20210906.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e271f258b6830fb924feb29e31a2ccae6ef6a2ed
--- /dev/null
+++ b/db/upgrade-20210906.sql
@@ -0,0 +1,3 @@
+SET ROLE 'mo_osmo';
+
+ALTER TYPE job_type ADD VALUE 'sort_scans';
diff --git a/mo/db.py b/mo/db.py
index 3be349480c76b0460b1044a51fe831e2f6b2426a..19964c4014bbd35e4fdf49f4206630e82fd292c2 100644
--- a/mo/db.py
+++ b/mo/db.py
@@ -633,6 +633,7 @@ class JobType(MOEnum):
     upload_feedback = auto()
     create_protocols = auto()
     process_scans = auto()
+    sort_scans = auto()
 
 
 class JobState(MOEnum):
diff --git a/mo/jobs/protocols.py b/mo/jobs/protocols.py
index 1b386d614748a74e168d5612a388cb4a172e03ca..f908fc81769c5edd6b9ba4d7146daaaf01c12572 100644
--- a/mo/jobs/protocols.py
+++ b/mo/jobs/protocols.py
@@ -353,3 +353,67 @@ def _process_scan_file(args: ScanJobArgs) -> List[ScanJobPage]:
         logger.debug(f'Scan: Strana #{page_nr}: {qr}')
 
     return output
+
+
+#
+# Job sort_scans: Roztřídí nascanované protokoly a založí jednotlivá řešení
+#
+# Je to recyklovaný process_scans job.
+#
+# Vstupní JSON (beze změny z process_scans):
+#        { 'contest_id': ID contestu,
+#          'site_id': ID soutěžního místa nebo none,
+#          'task_ids': [task_id, ...],
+#          'in_files': [názvy vstupních souborů]
+#        }
+#
+# Výstupní JSON:
+#        null
+
+
+def schedule_sort_scans(job_id: int, for_user: db.User) -> int:
+    # Znovupoužijeme starý job, jen mu změníme typ
+    the_job = TheJob(job_id)
+    job = the_job.load()
+    assert job is not None
+
+    sess = db.get_session()
+    contest = sess.query(db.Contest).options(joinedload(db.Contest.round)).get(job.in_json['contest_id'])
+    assert contest is not None
+
+    job.type = db.JobType.sort_scans
+    job.created_at = mo.now
+    job.expires_at = None
+    job.user = for_user
+    job.description = f'Rozdělení již roztříděných scanů {contest.round.round_code_short()}'
+
+    the_job.submit()
+    return the_job.job_id
+
+
+@job_handler(db.JobType.sort_scans)
+def handle_sort_scans(the_job: TheJob):
+    job = the_job.job
+    assert job.in_json is not None
+    contest_id = job.in_json['contest_id']  # type: ignore
+    site_id = job.in_json['site_id']        # type: ignore
+    task_ids = job.in_json['task_ids']      # type: ignore
+    in_files: List[str] = job.in_json['in_files']  # type: ignore
+
+    sess = db.get_session()
+    contest = sess.query(db.Contest).options(joinedload(db.Contest.round)).get(contest_id)
+    assert contest is not None
+    round = contest.round
+    round_code = round.round_code_short()
+
+    user_ids = set(u[0] for u in _get_user_id_query(contest, site_id).all())
+
+    tasks = sess.query(db.Task).filter(db.Task.task_id.in_(task_ids)).all()
+    tasks_by_code = {t.code: t for t in tasks}
+
+    # Jelikož se plánujeme zamyslet na dlouhou dobu, uzavřeme databázovou session.
+    sess.commit()
+
+    # TODO: paralelně rozstříhat a sestavit správná PDFka
+
+    # TODO: založit správná řešení
diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 4260a28de82988ba2ec04f17d4ab4cdbd98be9a4..752cae799e9c24aafa1e2a6286838422ab20cbaf 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -1901,7 +1901,9 @@ def org_contest_scans_process(ct_id: int, job_id: int, site_id: Optional[int] =
         if len(errors) > 0:
             flash('Nelze zpracovat, dokud kontrola vrací chyby. Nejdříve je opravte.')
             return redirect(self_url)
-        print("PROCESS")
+        mo.jobs.protocols.schedule_sort_scans(job_id, for_user=g.user)
+        flash('Skeny zařazeny pro zpracování, během několika chvil se řešení roztřídí k soutěžícím.', 'success')
+        return redirect(url_for('org_jobs'))
 
     def png_small(page: db.ScanPage) -> str:
         return ctx.url_for(