diff --git a/db/StoredFunctions.sql b/db/StoredFunctions.sql
index fad97d560710b7f28b88ccdcfeac00cdd410b1d9..c01515d5c266592740cd0a08df51bd3a3a0bce34 100644
--- a/db/StoredFunctions.sql
+++ b/db/StoredFunctions.sql
@@ -238,7 +238,8 @@ END//
 
 # create department or return existing
 DROP FUNCTION IF EXISTS CreateDepartment //
-CREATE FUNCTION CreateDepartment (
+DROP FUNCTION IF EXISTS Department_Create //
+CREATE FUNCTION Department_Create (
     `pName` VARCHAR(200) CHARSET utf8,
     `pFacultyID` INT(11)
 ) RETURNS INT(11) # department id or -1 if failed
@@ -253,6 +254,24 @@ BEGIN
     RETURN LAST_INSERT_ID();
 END //
 
+DROP FUNCTION IF EXISTS Department_Search //
+CREATE FUNCTION Department_Search (
+    `pName` VARCHAR(200) CHARSET utf8,
+    `pFacultyID` INT(11)
+) RETURNS INT(11) # department id of -1 if not found
+NO SQL
+BEGIN
+    DECLARE vID INT DEFAULT -1;
+
+    SELECT departments.ID INTO vID
+        FROM departments
+        WHERE   departments.Name = pName AND
+                departments.FacultyID = pFacultyID
+        LIMIT 1;
+
+    RETURN vID;
+END //
+
 # -------------------------------------------------------------------------------------------
 # Label: specializations
 # -------------------------------------------------------------------------------------------
@@ -577,7 +596,8 @@ END //
 
 
 DROP FUNCTION IF EXISTS CreateTeacher//
-CREATE FUNCTION `CreateTeacher` (
+DROP FUNCTION IF EXISTS Teacher_Create//
+CREATE FUNCTION `Teacher_Create` (
         `pLastName` VARCHAR(30) CHARSET utf8,
         `pFirstName` VARCHAR(30) CHARSET utf8,
         `pSecondName` VARCHAR(30) CHARSET utf8,
@@ -601,33 +621,9 @@ BEGIN
     INSERT INTO `teachers`
         (AccountID, LastName, FirstName, SecondName, JobPositionID, DepartmentID)
         VALUES  (vAccountID, pLastName, pFirstName, pSecondName, pJobPositionID, pDepartmentID);
-    RETURN ROW_COUNT()-1;
-END //
-
-
-DROP FUNCTION IF EXISTS CreateTeacherByDepName//
-CREATE FUNCTION `CreateTeacherByDepName` (
-        `pLastName` VARCHAR(30) CHARSET utf8,
-        `pFirstName` VARCHAR(30) CHARSET utf8,
-        `pSecondName` VARCHAR(30) CHARSET utf8,
-        `pDepartmentName` VARCHAR(200) CHARSET utf8,
-        `pFacultyID` INT,
-        `pActivationCode` VARCHAR(40) CHARSET utf8
-    )   RETURNS int(11) # 0 - success, < 0 - failed
-    NO SQL
-BEGIN
-    DECLARE vAccountID, vRoleID, vDepID INT DEFAULT -1;
-    DECLARE EXIT HANDLER FOR SQLEXCEPTION RETURN -1;
-
-    SET vDepID = CreateDepartment(pDepartmentName, pFacultyID);
-    IF vDepID < 0 THEN
-       RETURN -1;
-    END IF;
-
-    RETURN CreateTeacher(pLastName, pFirstName, pSecondName, 12, vDepID, pActivationCode);
+    RETURN LAST_INSERT_ID();
 END //
 
-
 -- -1 - не сотрудник деканата и не преподаватель дисциплины
 -- 0 - только чтение
 -- 1 - редактирование
diff --git a/db/StoredProcedures.sql b/db/StoredProcedures.sql
index cb6b058f6a6d34d29ca7370e3a34ef0cc409c40c..d128a636264a4a8388d95366a27dc49aa9ece2c3 100644
--- a/db/StoredProcedures.sql
+++ b/db/StoredProcedures.sql
@@ -104,6 +104,12 @@ BEGIN
         ORDER BY departments.Name ASC;
 END //
 
+DROP PROCEDURE IF EXISTS Departments_LoadAll//
+CREATE PROCEDURE `Departments_LoadAll` (
+) NO SQL
+BEGIN
+    SELECT * FROM `departments`;
+END //
 
 # -------------------------------------------------------------------------------------------
 # Label: specializations
diff --git a/media/components/office/dropzone/dz.js b/media/components/office/dropzone/dz.js
index 1b8854e281f29f7a7e3f887eb12713cf46e71d54..ecc936c34c36d26f64977e8fad8b65cecd4ec344 100644
--- a/media/components/office/dropzone/dz.js
+++ b/media/components/office/dropzone/dz.js
@@ -1,5 +1,5 @@
 var Dropzone = (function () {
-    var $dropzone, readerClbks = [];
+    var $dropzone, $input, readerClbks = [];
 
     var handlerOf = {
         dragOver: function (e) {
@@ -20,9 +20,13 @@ var Dropzone = (function () {
             $dropzone.removeClass('hover');
             processFiles(e.dataTransfer.files);
         },
-
-        fileSelect: function (evt) {
-            processFiles(evt.target.files);
+        
+        inputChange: function (e) {
+            if (e.value == '')
+                return;
+            
+            processFiles(e.target.files);
+            $input.value = '';
         }
     };
 
@@ -46,8 +50,8 @@ var Dropzone = (function () {
         init: function (listeners) {
             readerClbks = listeners || [];
 
-            var $input = document.getElementById('files');
-            $input.addEventListener('change', handlerOf.fileSelect, false);
+            $input = document.getElementById('files');
+            $input.addEventListener('change', handlerOf.inputChange, false);
 
             $dropzone = document.getElementsByClassName('b-dropzone')[0];
             $dropzone.addEventListener('dragover', handlerOf.dragOver, false);
diff --git a/media/components/office/teachers-upload/page.css b/media/components/office/teachers-upload/page.css
index 4968d16bf4df9e34ca4daf72c1bebe51c9a5f709..530bbb034b224cee30add5fc3788a32176d604ef 100644
--- a/media/components/office/teachers-upload/page.css
+++ b/media/components/office/teachers-upload/page.css
@@ -1,7 +1,3 @@
-pre {
-    margin-bottom: 20px;
-}
-
 code {
     padding: .1em .5em;
     font-size: 85%;
diff --git a/~dev_rating/application/classes/Controller/Handler/Teachers.php b/~dev_rating/application/classes/Controller/Handler/Teachers.php
index 10f924696db1247bc11097a80aa47d5a31ff3d68..bdebe75d7665f90ece789626d6fa67ad2917eb26 100644
--- a/~dev_rating/application/classes/Controller/Handler/Teachers.php
+++ b/~dev_rating/application/classes/Controller/Handler/Teachers.php
@@ -5,7 +5,7 @@ class Controller_Handler_Teachers extends Controller_Handler
     public function before() {
         parent::before();
 
-        $this->user->checkAccess(User::RIGHTS_ADMIN);
+        $this->user->checkAccess(User::RIGHTS_ADMIN | User::RIGHTS_DEAN);
     }
 
     protected function action_createTeacher() {
@@ -39,4 +39,40 @@ class Controller_Handler_Teachers extends Controller_Handler
 
         $this->response->body(json_encode($list));
     }
+
+    public function action_upload() {
+        $faculty = $this->user->Faculty;
+        if ($this->user->isAdmin() && $_POST['facultyID'])
+            $faculty = Model_Faculty::with($_POST['facultyID']);
+
+        $departments = Arr::groupByUniqueKey('Name', $faculty->getDepartments());
+        
+        foreach ($_POST['departments'] as $dep) {
+            if ($dep['name'] === 'Без кафедры')
+                $dep['name'] = '';
+            
+            $dep['ID'] = !isset($departments[$dep['name']])
+                ? self::createDepartment($dep['name'], $faculty)
+                : $departments[$dep['name']]['ID'];
+            
+            foreach ($dep['people'] as $name) {
+                list($lastName, $firstName, $secondName) = Text::parseFullName($name);
+
+                Model_Teacher::make()
+                    ->department($dep['ID'])
+                    ->firstName($firstName)
+                    ->lastName($lastName)
+                    ->secondName($secondName)
+                    ->create();
+            }
+        }
+    }
+    
+    private static function createDepartment($name, Model_Faculty $faculty) {
+        $sql = 'SELECT Department_Create(:name, :faculty) AS `id`';
+        return DB::query(Database::SELECT, $sql)
+            ->param(':name', $name)
+            ->param(':faculty', $faculty->ID)
+            ->execute()->get('id');
+    }
 }
diff --git a/~dev_rating/application/classes/FileParser.php b/~dev_rating/application/classes/FileParser.php
index 791dcb2b462fb9c8be18847fd40d09e9f3886ddd..267c42b587ef850d2bf9713dd39fcf2b9964bff7 100644
--- a/~dev_rating/application/classes/FileParser.php
+++ b/~dev_rating/application/classes/FileParser.php
@@ -22,7 +22,7 @@ class FileParser
             list($name, $gradeNum, $groupNum, $degree, $spec) = UTF8::clear($line);
 
             // Фамилия, имя, отчество
-            list($lastName, $firstName, $secondName) = self::parseFullName($name);
+            list($lastName, $firstName, $secondName) = Text::parseFullName($name);
 
             $degree = self::getDegreeType($degree);
 
@@ -87,53 +87,4 @@ class FileParser
             'RecordsExistsCount' => $exists,
         ];
     }
-
-
-    /**
-     * Parse teachers' info & synchronize it with database.
-     * @param $filename  string File with source data
-     * @param $facultyID int
-     * @return array
-     */
-    public static function uploadTeachers($filename, $facultyID) {
-        if (File::mime($filename) != 'text/plain')
-            return [];
-
-//        $faculties = [];
-//        foreach (Model_Faculties::load() as $faculty) {
-//            $name = trim($faculty['Name']);
-//            $faculties[$name] = $faculty['ID'];
-//        }
-
-        $count = 0;
-        $errors = [];
-        $file = fopen($filename, "r");
-
-        while ($line = fgetcsv($file, 0, ";")) {
-            list($name, $department, $facultyName) = UTF8::clear($line);
-
-            // Фамилия, имя, отчество
-            list($lastName, $firstName, $secondName) = self::parseFullName($name);
-
-//            if ($facultyName)
-//                $facultyID = $faculties[trim($facultyName)];
-
-            $attempt = Model_Account::createTeacherByDepName($lastName, $firstName, $secondName, $department, $facultyID);
-
-            if ($attempt == -1) {
-                $errors[] = ['Row' => $count, 'Info' => implode(';', $line)];
-            }
-
-            $count++;
-        }
-
-        return $errors;
-    }
-
-    private static function parseFullName($name) {
-        // Енотова (Сенченко) Наталья Лин Чу Геннадьевна
-        $name = preg_replace('/\(.*\)\s+/', '', $name);
-        $res = explode(' ', $name, 3);
-        return Arr::map('trim', $res);
-    }
-}
\ No newline at end of file
+}
diff --git a/~dev_rating/application/classes/Model/Account.php b/~dev_rating/application/classes/Model/Account.php
index c61c229303795d9ac09a7255b4b06419db733e17..fe583f1f9ee1e0f818779699d8c7fff18f35ae95 100644
--- a/~dev_rating/application/classes/Model/Account.php
+++ b/~dev_rating/application/classes/Model/Account.php
@@ -28,25 +28,6 @@ class Model_Account extends Model
         return UTF8::strtoupper($activationCode);
     }
 
-    public static function createTeacherByDepName($lastName, $firstName, $secondName, $department, $facultyID) {
-        if ($department == '') return -1;
-
-        $code = self::generateActivationCode();
-
-        $sql = "SELECT `CreateTeacherByDepName`(:last, :first, :second, :department, :faculty, :code) AS `UserID`;";
-        $response = DB::query(Database::SELECT, $sql)
-            ->parameters([
-                ':last'       => trim($lastName),
-                ':first'      => trim($firstName),
-                ':second'     => trim($secondName),
-                ':department' => $department,
-                ':faculty'    => $facultyID,
-                ':code'       => $code,
-            ])->execute()->get('UserID');
-
-        return $response == -1 ? -1 : $code;
-    }
-
     public static function createStudentEx($lastName, $firstName, $secondName, $gradeNum, $groupNum, $degree, $specialization, $facultyID, $semesterID = null) {
         $code = self::generateActivationCode();
 
diff --git a/~dev_rating/application/classes/Model/Faculties.php b/~dev_rating/application/classes/Model/Faculties.php
index d3b081c2be9c9f408cbb2576dda58edf4abae512..293270be64c14bb260733d8609d4a56f8ad404f7 100644
--- a/~dev_rating/application/classes/Model/Faculties.php
+++ b/~dev_rating/application/classes/Model/Faculties.php
@@ -8,4 +8,19 @@ class Model_Faculties extends Model
         return DB::query(Database::SELECT, $sql)
             ->execute()->as_array();
     }
+
+    public static function getDepartments() {
+        $sql = 'CALL `Departments_LoadAll`()';
+        $result = DB::query(Database::SELECT, $sql)->execute();
+        
+        $list = [];
+        foreach ($result as $row) {
+            if (empty($row['Name']))
+                $row['Name'] = 'Без кафедры';
+            
+            $list[] = $row;
+        }
+        
+        return $list;
+    }
 }
diff --git a/~dev_rating/application/classes/Model/Helper/TeacherBuilder.php b/~dev_rating/application/classes/Model/Helper/TeacherBuilder.php
index 9439373cecd41d1230519cbe2b509ac65b739659..46e9fd6b8d2253cb84fba8db1300345f1e1e59a5 100644
--- a/~dev_rating/application/classes/Model/Helper/TeacherBuilder.php
+++ b/~dev_rating/application/classes/Model/Helper/TeacherBuilder.php
@@ -4,8 +4,9 @@ class Model_Helper_TeacherBuilder extends Model_Helper_Builder
 {
     public function create() {
         $this->data += [
-            'SecondName' => '',
+            'SecondName'     => '',
             'ActivationCode' => Model_Account::generateActivationCode(),
+            'JobPositionID'  => Model_Teacher::JOB_TEACHER,
         ];
 
         $required = [
@@ -23,7 +24,7 @@ class Model_Helper_TeacherBuilder extends Model_Helper_Builder
         $string = trim($string);  # todo: remove empty symbols
         if (!strlen($string))
             throw new InvalidArgumentException('Last name can\'t be empty');
-        $this->data['LastName'] = $string;
+        $this->data['LastName'] = UTF8::clear($string);
         return $this;
     }
 
@@ -31,12 +32,12 @@ class Model_Helper_TeacherBuilder extends Model_Helper_Builder
         $string = trim($string);
         if (!strlen($string))
             throw new InvalidArgumentException('First name can\'t be empty');
-        $this->data['FirstName'] = $string;
+        $this->data['FirstName'] = UTF8::clear($string);
         return $this;
     }
 
     function & secondName($string) {
-        $this->data['SecondName'] = trim($string);
+        $this->data['SecondName'] = UTF8::clear($string);
         return $this;
     }
 
diff --git a/~dev_rating/application/classes/Model/Teacher.php b/~dev_rating/application/classes/Model/Teacher.php
index 52d91ed317b7e0ff6a9ebe2ce67448c3d13180f2..8630a4b1f99dc9211f46c98a1092342130f1a438 100644
--- a/~dev_rating/application/classes/Model/Teacher.php
+++ b/~dev_rating/application/classes/Model/Teacher.php
@@ -20,6 +20,12 @@
  */
 class Model_Teacher extends Model_Container
 {
+    // see `job_positions` table
+    const JOB_TEACHER = 9;
+    const JOB_PROFESSOR = 10;
+    const JOB_SENIOR_TEACHER = 11;
+
+
     protected function getRawData($id) {
         $sql = 'CALL `Teacher_GetInfo`(:id)';
         $info = DB::query(Database::SELECT, $sql)
@@ -40,7 +46,7 @@ class Model_Teacher extends Model_Container
     }
 
     protected function create() {
-        $sql = 'SELECT `CreateTeacher`(LastName, FirstName, SecondName, JobPositionID, DepID, ActivationCode) AS `ID`';
+        $sql = 'SELECT `Teacher_Create`(LastName, FirstName, SecondName, JobPositionID, DepID, ActivationCode) AS `ID`';
 
         $this->data[self::$ID_FIELD] = DB::query(Database::SELECT, $sql)
             ->parameters($this->data)
diff --git a/~dev_rating/application/classes/User.php b/~dev_rating/application/classes/User.php
index 8a7f40bee0c0d5154dd1cb70b7bcb0459fb056a7..53ea6e0271ff122ff5c3387618589103550b3b67 100644
--- a/~dev_rating/application/classes/User.php
+++ b/~dev_rating/application/classes/User.php
@@ -77,10 +77,10 @@ class User implements ArrayAccess
 
         $session = $this->_session = Session::instance();
 
-        if ( !isset($session->RoleMark) )
-            $session->RoleMark = 1;
+        if (!isset($session['RoleMark']))
+            $session['RoleMark'] = 1;
 
-        if ( self::$_updateSession ) {
+        if (self::$_updateSession) {
             $session->regenerate();
             $session->set('start_time', time());
         }
diff --git a/~dev_rating/application/views/office/teachers/upload.twig b/~dev_rating/application/views/office/teachers/upload.twig
index 866d68d66c1d8fea0c2c5c2753e6f33e52abb3b6..0a6c72d0ec170e3b34138cce7e9c69f6ea41c99e 100644
--- a/~dev_rating/application/views/office/teachers/upload.twig
+++ b/~dev_rating/application/views/office/teachers/upload.twig
@@ -12,16 +12,41 @@
 
     <script>
         $(function () {
+            var data = [];
+
             Dropzone.init([
                 function (file, content) {
                     if (file.type !== 'text/plain') {
                         return EventInspector.error('Неверный формат файла!');
                     }
 
+                    $('.b-upload-first-step').hide();
+                    $('.b-upload-second-step').show();
+                    
                     var list = content.split('\n');
-                    console.log(parseTeachers(list));
+                    data = data.concat(parseTeachers(list));
                 }]);
-        })
+            
+            $('#cancel').click(function () {
+                $('.b-upload-second-step').hide();
+                $('.b-upload-first-step').show();
+                $('#save').removeAttr('disabled').text('Сохранить');
+                data = [];
+            });
+
+            $('#save').click(function () {
+                $(this).attr('disabled', 'disabled')
+                    .html('<i class="fa fa-circle-o-notch fa-spin"></i>');
+                
+                $.post(g_URLdir + 'handler/teachers/upload', {
+                    facultyID: $('#faculty').val(),
+                    departments: data
+                }, function (res) {
+                    EventInspector.success('Успешно сохранено!');
+                    $('#cancel').trigger('click');
+                });
+            });
+        });
     </script>
 {% endblock %}
 
@@ -30,12 +55,12 @@
 {% block office_content %}
     <h2 class="defaultForm">Пакетная загрузка преподавателей</h2>
 
-    <div class="b-upload-file">
+    <div class="b-upload-first-step">
         <p>
             Инструмент пакетной загрузки преподавателей предоставляет возможность регистрировать в системе множество преподавателей.
             Файл со списком преподавателей представляет собой <code>.txt</code>-файл, который должен иметь следующий формат:
 
-        <pre>
+        <pre style="margin-bottom:20px">
             {% for field in ['Кафедра <название>', '', 'ФИО', 'ФИО'] %}
                 {{ field }}<br/>
             {% endfor %}
@@ -52,23 +77,34 @@
         {{ HTML.component('office/dropzone/dz.twig')|raw }}
     </div>
 
+    <div class="b-upload-second-step" style="display:none">
+        {% if User.isAdmin %}
+            <p>Выберите факультет, на который нужно загрузить преподавателей:</p>
+            
+            <select id="faculty" class="defaultForm">
+                {% for row in Faculties %}
+                    {% set selected = (row.ID == User.FacultyID ? 'selected="selected"') %}
+                    <option value="{{ row.ID }}" {{ selected }}>
+                        {{ row.Name }} ({{ row.Abbr }})
+                    </option>
+                {% endfor %}
+            </select>
+        {% endif %}
+
+        {# todo: show some results & errors #}
+        {#<div id="problems" style="margin-top: 20px; display:none">#}
+            {#<p>В базе данных отсутствуют следующие кафедры, создать их?#}
+
+                 {#&#123;&#35;todo: make editable area &#35;&#125;#}
+            {#<pre></pre>#}
+        
+            {#<p>Следующие имена некорректны:</p>#}
+        {#</div>#}
+
+        <div style="float:right;margin-top:5px">
+            <button id="save" class="defaultForm BlueButton">Сохранить</button>
+            или <a id="cancel" href="#">отменить</a>
+        </div>
+    </div>
 
-    {#<div class="dialogTopText">#}
-
-    {#</div>#}
-
-    {#<form enctype="multipart/form-data" action="" method="POST">#}
-    {#<div class="goodClearFix defaultForm marginBetween">#}
-    {#<select id="facultySelect" name="facultyID" class="defaultForm">#}
-    {#<option value="0" selected="selected">— Выберите подразделение ЮФУ —</option>#}
-    {#{% for row in Faculties %}#}
-    {#<option value="{{ row.ID }}">{{ row.Name }} ({{ row.Abbr }})</option>#}
-    {#{% endfor %}#}
-    {#</select>#}
-    {#</div>#}
-    {#<div class="goodClearFix">#}
-    {#<div class="defaultForm FLeft"><input name="students" class="defaultForm FullWidth" type="file"></div>#}
-    {#<div class="defaultForm FRight"><input type="submit" class="defaultForm GreenButton P2Width noMargin" value="Загрузить"></div>#}
-    {#</div>#}
-    {#</form>#}
 {% endblock %}
diff --git a/~dev_rating/static/other/teachers.txt b/~dev_rating/static/other/teachers.txt
index 66750fdb68c92e3773212c4b45de3be534c97718..f470b234a957177f45a3319d5c63c2b4e84b9df5 100644
--- a/~dev_rating/static/other/teachers.txt
+++ b/~dev_rating/static/other/teachers.txt
@@ -16,4 +16,4 @@
 
 Энакин Скайуокер
 Оби-Ван Кеноби
-Йода
+Мастер Йода
diff --git a/~dev_rating/system/classes/Kohana/Text.php b/~dev_rating/system/classes/Kohana/Text.php
index d9f36a63ac0720cf095b7faeba7dc8b314c587ff..dda030f6a6751da45a0346db758b335900511f7d 100644
--- a/~dev_rating/system/classes/Kohana/Text.php
+++ b/~dev_rating/system/classes/Kohana/Text.php
@@ -10,6 +10,13 @@
  */
 class Kohana_Text {
 
+    public static function parseFullName($name) {
+        // Енотова (Сенченко) Наталья Лин Чу Геннадьевна ♥
+        $name = preg_replace('/\(.*\)\s+/', '', $name);
+        $res = explode(' ', $name, 3);
+        return Arr::map('trim', $res);
+    }
+
     public static function dump($data) { var_dump($data); }
 
     // take array with (at least) LastName & FirstName keys