diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 26f566e11318e46703842d8c03d08cfafbd2b832..62392153028b741d9ef2e83e59d4612df37fe86f 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -2,10 +2,12 @@ from dataclasses import dataclass
 from flask import render_template, g, redirect, url_for, flash, request
 from flask_wtf import FlaskForm
 import flask_wtf.file
+import locale
 from markupsafe import Markup
-from sqlalchemy import func, and_
+from sqlalchemy import func, and_, select
 from sqlalchemy.orm import joinedload, aliased
 from sqlalchemy.orm.query import Query
+from sqlalchemy.dialects.postgresql import insert as pgsql_insert
 from typing import Any, List, Tuple, Optional, Sequence, Dict
 import werkzeug.exceptions
 import wtforms
@@ -1187,3 +1189,99 @@ def org_contest_user(contest_id: int, user_id: int):
         paper_link=lambda u, p: mo.web.util.org_paper_link(sc.contest, None, u, p),
         paper_counts=paper_counts,
     )
+
+
+class AdvanceForm(FlaskForm):
+    boundary = IntegerField('Bodová hranice', description="Postupí všichni účastníci, kteří v minulém kole získali aspoň tolik bodů.", validators=[validators.InputRequired()])
+    status = wtforms.HiddenField()
+    preview = wtforms.SubmitField('Zobrazit návrh')
+    execute = wtforms.SubmitField('Provést')
+
+
+@app.route('/org/contest/c/<int:contest_id>/advance', methods=('GET', 'POST'))
+def org_contest_advance(contest_id: int):
+    sess = db.get_session()
+    conn = sess.connection()
+    contest, rr = get_contest_rr(contest_id, Right.manage_contest)
+
+    round = contest.round
+    if round.state != db.RoundState.preparing:
+        flash('Aktuální kolo není ve stavu přípravy', 'danger')
+        return redirect(url_for('org_contest', id=contest_id))
+
+    prev_round = sess.query(db.Round).filter_by(year=round.year, category=round.category, seq=round.seq - 1).one_or_none()
+    if prev_round is None:
+        flash('Předchozí kolo nenalezeno', 'danger')
+        return redirect(url_for('org_contest', id=contest_id))
+    elif prev_round.state != db.RoundState.closed:
+        flash('Předchozí kolo dosud nebylo ukončeno', 'danger')
+        return redirect(url_for('org_contest', id=contest_id))
+    elif prev_round.level < round.level:
+        flash('Předchozí kolo se koná ve vyšší oblasti než toto kolo', 'danger')
+        return redirect(url_for('org_contest', id=contest_id))
+
+    prev_contests: List[db.Contest] = []
+    accept_by_place_id: Dict[int, int] = {}
+    reject_by_place_id: Dict[int, int] = {}
+
+    form = AdvanceForm()
+    if form.validate_on_submit():
+        desc_cte = db.place_descendant_cte(contest.place, max_level=prev_round.level)
+        prev_contests = (sess.query(db.Contest)
+                         .filter(db.Contest.round == prev_round)
+                         .filter(db.Contest.place_id.in_(select([desc_cte])))
+                         .options(joinedload(db.Contest.place))
+                         .all())
+        prev_contests.sort(key=lambda c: locale.strxfrm(c.place.name or ""))
+
+        accept_by_place_id = {c.place_id: 0 for c in prev_contests}
+        reject_by_place_id = {c.place_id: 0 for c in prev_contests}
+
+        prev_pion_query = (sess.query(db.Participation)
+                           .filter(db.Participation.contest_id.in_([c.contest_id for c in prev_contests]))
+                           .filter(db.Participation.state.in_((db.PartState.registered, db.PartState.invited, db.PartState.present))))
+        prev_pions = prev_pion_query.all()
+
+        if form.boundary.data > 0:
+            accept_uids = (sess.query(db.Solution.user_id)
+                           .select_from(db.Solution)
+                           .join(db.Task, and_(db.Task.task_id == db.Solution.task_id, db.Task.round == prev_round))
+                           .filter(db.Solution.user_id.in_(prev_pion_query.with_entities(db.Participation.user_id).subquery()))
+                           .group_by(db.Solution.user_id)
+                           .having(func.sum(db.Solution.points) >= form.boundary.data)
+                           .all())
+            accept_uids = [a[0] for a in accept_uids]
+        else:
+            accept_uids = None
+
+        want_execute = form.execute.data
+        for pp in prev_pions:
+            if accept_uids and pp.user_id not in accept_uids:
+                reject_by_place_id[pp.place_id] += 1
+                continue
+            accept_by_place_id[pp.place_id] += 1
+
+            if want_execute:
+                # ORM neumí ON CONFLICT DO NOTHING, takže musíme o vrstvu níže
+                ins = pgsql_insert(db.Participation.__table__).values(
+                    user_id=pp.user_id,
+                    contest_id=contest.contest_id,
+                    place_id=contest.place.place_id,
+                    state=db.PartState.invited,
+                ).on_conflict_do_nothing()
+                conn.execute(ins)
+
+        if want_execute:
+            sess.commit()
+            flash('Provedeno.', 'success')
+
+    return render_template(
+        'org_contest_advance.html',
+        contest=contest,
+        round=contest.round,
+        prev_round=prev_round,
+        prev_contests=prev_contests,
+        accept_by_place_id=accept_by_place_id,
+        reject_by_place_id=reject_by_place_id,
+        form=form,
+    )
diff --git a/mo/web/templates/org_contest.html b/mo/web/templates/org_contest.html
index b91335053dbfcc99440bfdcfb5fb5849af74d90f..46315999d2421c81e6ce88c16de79982645e6574 100644
--- a/mo/web/templates/org_contest.html
+++ b/mo/web/templates/org_contest.html
@@ -32,11 +32,16 @@
 
 <div class="btn-group">
 	<a class="btn btn-primary" href='{{ url_for('org_contest_list', id=contest.contest_id, site_id=site_id) }}'>Seznam účastníků</a>
+	{% if round.state != RoundState.preparing %}
 	<a class="btn btn-primary" href='{{ url_for('org_contest_solutions', id=contest.contest_id, site_id=site_id) }}'>Odevzdaná řešení</a>
+	{% endif %}
 	{% if not site %}
 	{% if round.state in [RoundState.grading, RoundState.closed] %}
 	<a class="btn btn-primary" href='{{ url_for('org_score', contest_id=contest.contest_id) }}'>Výsledky</a>
 	{% endif %}
+	{% if round.state == RoundState.preparing and round.seq > 1 %}
+	<a class="btn btn-primary" href='{{ url_for('org_contest_advance', contest_id=contest.contest_id) }}'>Postup z minulého kola</a>
+	{% endif %}
 	{% if can_manage %}
 	<a class="btn btn-default" href='{{ url_for('org_contest_import', id=contest.contest_id) }}'>Importovat data</a>
 	{% endif %}
diff --git a/mo/web/templates/org_contest_advance.html b/mo/web/templates/org_contest_advance.html
new file mode 100644
index 0000000000000000000000000000000000000000..f7a86b84908938777a2f9327a3e063d4fc28c6b7
--- /dev/null
+++ b/mo/web/templates/org_contest_advance.html
@@ -0,0 +1,41 @@
+{% extends "base.html" %}
+{% import "bootstrap/wtf.html" as wtf %}
+
+{% block title %}Postup z {{ prev_round.round_code() }} ({{ prev_round.name }}) do {{ round.round_code() }} ({{ round.name }}){% endblock %}
+{% block breadcrumbs %}
+{{ contest_breadcrumbs(round=round, contest=contest, action="Postup") }}
+{% endblock %}
+{% block body %}
+
+<form method="POST" class="form form-horizontal" action="">
+	{{ form.csrf_token }}
+	{{ wtf.form_field(form.boundary, form_type='horizontal') }}
+	<div class="btn-group col-lg-offset-2">
+		{{ wtf.form_field(form.preview) }}
+		{{ wtf.form_field(form.execute, class="btn btn-primary") }}
+	</div>
+</form>
+
+{% if form.preview.data or form.execute.data %}
+<h3>Postup z oblastí</h3>
+
+<table class='data'>
+	<thead>
+		<tr><th>Oblast<th>Postoupilo<th>Nepostoupilo
+	<tbody>
+		{% for c in prev_contests %}
+		<tr>
+			<td>{{ c.place.name }}
+			<td>{{ accept_by_place_id[c.place.place_id] }}
+			<td>{{ reject_by_place_id[c.place.place_id] }}
+		{% endfor %}
+	<tfoot>
+		<tr>
+			<th>Celkem
+			<th>{{ accept_by_place_id.values()|sum }}
+			<th>{{ reject_by_place_id.values()|sum }}
+	</tfoot>
+</table>
+{% endif %}
+
+{% endblock %}