diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index 32e87f4d0deee0e6ea8627e8ce358e5858c068fc..e8a5945a531b5f0ec57e3f6c87cf52e2dbd551ea
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@ Thumbs.db
 *.project
 *.idea
 *nbproject
+.settings
+.buildpath
diff --git a/db/constraints/base.sql b/db/constraints/base.sql
index 1b7377fbcc4b2dda617ae4f4a2de30aaa8bd98e8..a1aa6706a64e53302d618cb4ba6aa3b79972f320 100644
--- a/db/constraints/base.sql
+++ b/db/constraints/base.sql
@@ -136,3 +136,9 @@ ALTER TABLE `exam_period_options`
   ADD CONSTRAINT `exam_period_options_ibfk_2` FOREIGN KEY (`StudentID`) REFERENCES `students` (`ID`),
   ADD CONSTRAINT `exam_period_options_ibfk_1` FOREIGN KEY (`SubmoduleID`) REFERENCES `submodules` (`ID`);
 
+--
+-- Ограничения внешнего ключа таблицы `compound_disciplines`
+--
+ALTER TABLE `compound_disciplines`
+  ADD CONSTRAINT `compound_disciplines_ibfk_1` FOREIGN KEY (`SpecializationID`) REFERENCES `specializations` (`ID`),
+  ADD CONSTRAINT `compound_disciplines_ibfk_2` FOREIGN KEY (`GradeID`) REFERENCES `grades` (`ID`);
diff --git a/db/fixes/01_09_15.sql b/db/fixes/01_09_15.sql
new file mode 100644
index 0000000000000000000000000000000000000000..a5cca162b85153e9618975e3d411e3abfba55d90
--- /dev/null
+++ b/db/fixes/01_09_15.sql
@@ -0,0 +1,27 @@
+DELIMITER //
+
+DROP TABLE IF EXISTS `compound_disciplines`//
+
+CREATE TABLE IF NOT EXISTS `compound_disciplines` (
+  `ID` int(11) NOT NULL AUTO_INCREMENT,
+  `Name` varchar(200) CHARACTER SET utf8 NULL DEFAULT 'Курс по выбору',
+  `GradeID` int(11) NOT NULL,
+  `SpecializationID` int(11) NOT NULL,
+  PRIMARY KEY (`ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 //
+
+
+ALTER TABLE `disciplines`
+  ADD `CompoundDiscID` INT(11) DEFAULT NULL,
+  ADD KEY `CompoundDiscID` (`CompoundDiscID`)//
+
+ALTER TABLE `disciplines`
+  ADD CONSTRAINT `disciplines_ibfk_6` FOREIGN KEY (`CompoundDiscID`) REFERENCES `compound_disciplines` (`ID`) //
+
+ALTER TABLE `compound_disciplines`
+  ADD CONSTRAINT `compound_disciplines_ibfk_1` FOREIGN KEY (`SpecializationID`) REFERENCES `specializations` (`ID`),
+  ADD CONSTRAINT `compound_disciplines_ibfk_2` FOREIGN KEY (`GradeID`) REFERENCES `grades` (`ID`);
+
+DELIMITER ;
+
+
diff --git a/db/stored/procedures.sql b/db/stored/procedures.sql
index 6d83516fd4fe5a472cc68327af36c074bb7bfa7d..d2e06552f69f05b87adc3229a3209899c44d8bcc 100644
--- a/db/stored/procedures.sql
+++ b/db/stored/procedures.sql
@@ -776,6 +776,7 @@ BEGIN
         view_disciplines.IsLocked,
         view_disciplines.Milestone,
         view_disciplines.Subtype,
+        view_disciplines.CompoundDiscID,
         vIsBonus AS 'IsBonus',
         semesters.Num AS 'semesterNum', # TODO: Camelize
         semesters.Year AS 'semesterYear'
@@ -806,7 +807,16 @@ BEGIN
         ORDER BY view_disciplines.SubjectName ASC;
 END //
 
-
+DROP PROCEDURE IF EXISTS GetCompoundDisciplinesForGrade//
+CREATE PROCEDURE `GetCompoundDisciplinesForGrade` (
+    IN `pGradeID` INT
+) NO SQL
+BEGIN
+    SELECT  compound_disciplines.ID,
+            compound_disciplines.Name
+        FROM `compound_disciplines`
+        WHERE compound_disciplines.GradeID = pGradeID;
+END //
 
 # processed format of output (after desequentialization)
 # { discipline1 {group1, group2, ...}, discipline2 {groupN, ...}, ... }
@@ -957,6 +967,28 @@ BEGIN
         FROM tDisc
         INNER JOIN `view_disciplines` ON tDisc.DisciplineID = view_disciplines.DisciplineID
         WHERE view_disciplines.SemesterID = pSemesterID;
+# TODO: добавить этот код, т.к. он связан с CompoundDisciplines
+#             view_disciplines.ExamType  AS 'Type',
+#             view_disciplines.CompoundDiscID,
+#             view_disciplines.CompoundDiscName
+#         FROM `disciplines_groups`
+#         INNER JOIN `view_disciplines` ON    view_disciplines.DisciplineID = disciplines_groups.DisciplineID AND
+#                                             view_disciplines.SemesterID = pSemesterID
+#         WHERE disciplines_groups.GroupID = pGroupID
+#     ) UNION DISTINCT
+#     (SELECT view_disciplines.DisciplineID  AS 'ID',
+#             view_disciplines.SubjectName,
+#             view_disciplines.Subtype,
+#             view_disciplines.ExamType AS 'Type'
+#         FROM `disciplines_students`
+#         INNER JOIN `students` ON disciplines_students.StudentID = students.ID
+#         INNER JOIN `view_disciplines` ON view_disciplines.DisciplineID = disciplines_students.DisciplineID AND
+#                                          view_disciplines.SemesterID = pSemesterID
+#         INNER JOIN `students_groups` ON students_groups.StudentID = students.ID AND
+#                                         students_groups.SemesterID = pSemesterID
+#         WHERE students_groups.GroupID = pGroupID
+#     );
+
 END //
 
 
@@ -965,6 +997,19 @@ END //
 # Label: rating
 # -------------------------------------------------------------------------------------------
 
+# TODO: проверить зачем это нужно
+DROP PROCEDURE IF EXISTS GetDisciplineRateInfo//
+CREATE PROCEDURE `GetDisciplineRateInfo` (
+    IN `pDisciplineID` INT
+) NO SQL
+BEGIN
+    SELECT  view_disciplines_results.DisciplineRateMax AS 'Max',
+            view_disciplines_results.DisciplineRateCur AS 'Current'
+        FROM `view_disciplines_results`
+        WHERE view_disciplines_results.DisciplineID = pDisciplineID
+        LIMIT 1;
+END//
+
 # TODO: merge with GetRatesForGroupByStage
 DROP PROCEDURE IF EXISTS GetRatesForGroup//
 CREATE PROCEDURE `GetRatesForGroup` (
@@ -1488,5 +1533,73 @@ BEGIN
     LIMIT 1;
 END //
 
+# something useful! may be is replaced yet!
+DROP PROCEDURE IF EXISTS GetRatesForGroupByStage2//
+CREATE PROCEDURE `GetRatesForGroupByStage2` (
+    IN `pDisciplineID` INT,
+    IN `pGroupID` INT,
+    IN `pMilestone` INT
+) NO SQL
+BEGIN
+    DECLARE vSemesterID, vGroupID INT DEFAULT -1;
+    SET vSemesterID = GetDisciplineProperty(pDisciplineID, 'semester');
+    DROP TABLE IF EXISTS tStudents;
+    CREATE TEMPORARY TABLE tStudents (
+        `StudentID` INT NOT NULL
+    );
+
+    # check that group attached to discipline. Otherwise vGroupID = -1;
+    SELECT disciplines_groups.GroupID INTO vGroupID
+        FROM `disciplines_groups`
+        WHERE   disciplines_groups.GroupID = pGroupID AND
+                disciplines_groups.DisciplineID = pDisciplineID
+        LIMIT 1;
+
+    # get all students from group, that take this discipline
+    IF vGroupID <= 0 THEN # doesn't attached
+        INSERT INTO tStudents (`StudentID`)
+        SELECT disciplines_students.StudentID
+            FROM `disciplines_students`
+            WHERE   disciplines_students.DisciplineID = pDisciplineID AND
+                    disciplines_students.Type = 'attach';
+    ELSE # attached group
+        INSERT INTO tStudents (`StudentID`)
+        SELECT students_groups.StudentID
+            FROM `students_groups`
+            LEFT JOIN `disciplines_students` ON disciplines_students.StudentID = students_groups.StudentID AND
+                                                disciplines_students.DisciplineID = pDisciplineID
+            WHERE   students_groups.GroupID = vGroupID AND
+                    students_groups.SemesterID = vSemesterID AND
+                    NOT disciplines_students.Type <=> 'detach'; # exclude detached students
+    END IF;
+
+    SELECT  tRes.*,
+            students.LastName,
+            students.FirstName,
+            students.SecondName
+        FROM (
+            SELECT  tStudents.StudentID,
+                    SUM(tRate.Rate*(tMap.ModuleType = 'regular')) AS 'Semester',
+                    SUM(tRate.Rate*(tMap.ModuleType = 'bonus')) AS 'Bonus',
+                    SUM(tRate.Rate*(tMap.ModuleType = 'extra')*(tMap.SubmoduleOrderNum < pMilestone)) AS 'Extra',
+                    SUM(tRate.Rate*(tMap.ModuleType = 'extra')*(tMap.SubmoduleOrderNum < pMilestone - 1)) AS 'PreviousExtra',
+                    SUM(tRate.Rate*(tMap.ModuleType = 'exam')*(tMap.SubmoduleOrderNum = pMilestone)) AS 'Exam',
+                    MAX(tRate.Rate*(tMap.ModuleType = 'exam')*(tMap.SubmoduleOrderNum < pMilestone)) AS 'PreviousExam',
+                    MAX(tRate.Rate*(tMap.ModuleType = 'exam')*(tMap.SubmoduleOrderNum < pMilestone - 1)) AS 'PrePreviousExam',
+                    MAX(IF(tMap.SubmoduleOrderNum = pMilestone, exam_period_options.TYPE, NULL)) As 'Option',
+                    MAX(IF(exam_period_options.TYPE = 'pass', 1, 0)) As 'AutoPassed'
+                FROM `tStudents`
+                LEFT JOIN `view_roadmap` AS tMap   ON  tMap.DisciplineID = pDisciplineID
+                LEFT JOIN `rating_table` AS tRate  ON  tRate.StudentID = tStudents.StudentID AND
+                                                    tRate.SubmoduleID = tMap.SubmoduleID
+                LEFT JOIN `exam_period_options` ON  exam_period_options.submoduleID = tMap.SubmoduleID AND
+                                                    exam_period_options.StudentID = tStudents.StudentID
+                GROUP BY    tStudents.StudentID
+            ) tRes
+        INNER JOIN `students` ON students.ID = tRes.StudentID
+        ORDER BY    students.LastName ASC,
+                    students.FirstName ASC,
+                    students.SecondName ASC;
+END //
 
 DELIMITER ;
diff --git a/db/stored/views.sql b/db/stored/views.sql
index e1fe5181d2f88b6d8895a14f5523dcbbc6fa5369..48646e0130efef64ddfbdb18ea3f96fe3878fe30 100644
--- a/db/stored/views.sql
+++ b/db/stored/views.sql
@@ -13,8 +13,6 @@ DROP VIEW IF EXISTS `view_rating_result`;
 
 
 
-
-
 CREATE OR REPLACE VIEW `view_groups` AS
     SELECT  study_groups.ID         AS 'GroupID',
             study_groups.GroupNum   AS 'GroupNum',
@@ -78,6 +76,7 @@ CREATE OR REPLACE VIEW `view_disciplines` AS
             disciplines.IsLocked,
             disciplines.Milestone,
             disciplines.Subtype,
+            disciplines.CompoundDiscID,
             grades.ID               AS 'GradeID',
             grades.Num              AS 'GradeNum',
             grades.Degree,
@@ -86,11 +85,14 @@ CREATE OR REPLACE VIEW `view_disciplines` AS
             subjects.Abbr           AS 'SubjectAbbr',
             faculties.ID            AS 'FacultyID',
             faculties.Name          AS 'FacultyName',
-            faculties.Abbr          AS 'FacultyAbbr'
+            faculties.Abbr          AS 'FacultyAbbr',
+            compound_disciplines.Name AS 'CompoundDiscName'
         FROM `disciplines`
         INNER JOIN `subjects` ON subjects.ID = disciplines.SubjectID
         INNER JOIN `faculties` ON faculties.ID = disciplines.FacultyID
-        INNER JOIN `grades` ON grades.ID = disciplines.GradeID;
+        INNER JOIN `grades` ON grades.ID = disciplines.GradeID
+        LEFT JOIN `compound_disciplines` ON compound_disciplines.ID = disciplines.CompoundDiscID;
+
 
 
 
@@ -120,4 +122,3 @@ CREATE OR REPLACE VIEW `view_disciplines_results` AS
         WHERE   modules.Type = 'regular' OR
                 (modules.Type = 'exam' AND submodules.OrderNum = 1)
         GROUP BY disciplines.ID;
-
diff --git a/db/structure/base.sql b/db/structure/base.sql
index c76978de0a4bcc1adcaaa9ea5c4f82d14c949d1a..7f8a30a157fe393305f7cc66e27e8a68ced54863 100644
--- a/db/structure/base.sql
+++ b/db/structure/base.sql
@@ -64,10 +64,28 @@ CREATE TABLE IF NOT EXISTS `departments` (
 
 -- --------------------------------------------------------
 
+--
+-- Структура таблицы `compound_disciplines`
+--
+
+-- --------------------------------------------------------
+CREATE TABLE IF NOT EXISTS `compound_disciplines` (
+  `ID` int(11) NOT NULL AUTO_INCREMENT,
+  `Name` varchar(200) CHARACTER SET utf8 NULL DEFAULT 'Курс по выбору',
+  `GradeID` int(11) NOT NULL,
+  `SpecializationID` int(11) NOT NULL,
+  PRIMARY KEY (`ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
 --
 -- Структура таблицы `disciplines`
 --
 
+-- --------------------------------------------------------
+
+
 CREATE TABLE IF NOT EXISTS `disciplines` (
   `ID` int(11) NOT NULL AUTO_INCREMENT,
   `GradeID` int(11) NOT NULL,
@@ -444,4 +462,3 @@ CREATE TABLE IF NOT EXISTS `exam_period_options` (
   KEY `StudentID_2` (`StudentID`),
   KEY `StudentID_3` (`StudentID`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
-
diff --git a/deploy/phpConfig/database.php b/deploy/phpConfig/database.php
index 73f0a6bb167d1186e641a5ece060a82466e3d18b..97c4fbf5b398039dd3554cca51be20a3d5ac4d88 100644
--- a/deploy/phpConfig/database.php
+++ b/deploy/phpConfig/database.php
@@ -15,7 +15,7 @@ return array
 			 */
 			'dsn'        => 'mysql:host=127.0.0.1;dbname=mmcs_rating',
 			'username'   => 'mmcs_rating',
-			'password'   => 'Pefnesdy',
+-			'password'   => 'Pefnesdy',
 			'persistent' => FALSE,
 			'options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')
 		),
diff --git a/media/js/dean/bill.js b/media/js/dean/bill.js
new file mode 100644
index 0000000000000000000000000000000000000000..b67cddd90bc50d2ae19590e9d4737428648a950f
--- /dev/null
+++ b/media/js/dean/bill.js
@@ -0,0 +1,45 @@
+$(function () {
+    var jSemesterSelect = $('#semesterSelect');
+    var jGradeSelect = $('#gradeSelect');
+    var jGroupSelect = $('#groupSelect');
+    var jSpinner = $('.loadingSpinner');
+    var jContent = $('.billContent');
+
+    jSemesterSelect.change(function () {
+        var sem_id = jSemesterSelect.find('option:selected').val();
+        if (sem_id != 0) {
+            jGradeSelect.prop('disabled', false);
+        } else {
+            jGradeSelect.prop('disabled', true);
+            jGroupSelect.prop('disabled', true);
+        }
+    });
+
+    jGradeSelect.change(function () {
+        var gr_id = jGradeSelect.find('option:selected').val();
+        if (gr_id) {
+            jGroupSelect.prop('disabled', false);
+            $.post(URLdir + 'handler/Transfer/getGroups',
+                {'gradeID': jGradeSelect.find('option:selected').val()},
+                function (data) {
+                    jGroupSelect.html(data);
+                });
+        } else {
+            jGroupSelect.prop('disabled', true);
+        }
+    });
+
+    jGroupSelect.change(function () {
+        var gr_id = jGroupSelect.find('option:selected').val();
+        if (gr_id) {
+            jContent.css('display', 'none');
+            jSpinner.css('display', 'block');
+            $.post(URLdir + 'office/bill/group/' + gr_id,
+                function (data) {
+                    jSpinner.css('display', 'none');
+                    jContent.html(data);
+                    jContent.css('display', 'block');
+                });
+        }
+    });
+});
\ No newline at end of file
diff --git a/media/js/discipline/createDiscipline.js b/media/js/discipline/createDiscipline.js
index bbc91a58678f941c8e0fe5d0549710e26af1d6ab..5b5040f6429ed4c9ab1babb9346ad416619be24c 100644
--- a/media/js/discipline/createDiscipline.js
+++ b/media/js/discipline/createDiscipline.js
@@ -5,6 +5,7 @@ $(function() {
     var jSubjectInput = $("input.InputSubject ");
     var jGradeSelect = $("select.SelectGrade");
     var jExamTypeRadio = $("input.ExamType");
+    var jCompoundSelect = $("select.SelectCompound");
 
     // Изменения базовых параметров дисциплины
     $(".AddDiscipline").click(function()
@@ -26,6 +27,8 @@ $(function() {
             ++errCount;
         }
 
+        var CompoundID = parseInt(jCompoundSelect.val());
+
         var examType = jExamTypeRadio.filter(":radio:checked").val();
         if (examType === undefined) {
             jExamTypeRadio.first().parent().parent().css("background-color", "#f2b4b4");
@@ -70,8 +73,16 @@ $(function() {
     });
 
     jGradeSelect.change(function() {
-        if (parseInt($(this).val()) > 0)
+        if (parseInt($(this).val()) > 0) {
             jGradeSelect.css("border-color", INPUT_BORDER_COLOR);
+            jCompoundSelect.prop("disabled", false);
+            $.post(g_URLdir + "handler/discipline/getCompounds", {'gradeID' : parseInt($(this).val()) },
+                function(data){
+                    jCompoundSelect.html(data.response);
+                }, "json");
+        }else{
+            jCompoundSelect.prop("disabled", true);
+        }
     });
 
     jExamTypeRadio.change(function() {
@@ -93,5 +104,5 @@ $(function() {
     $("input.InputPracticeCount").keydown(function(event) {
         KeyDownOnlyNumber(event);
     });
-    
-});
\ No newline at end of file
+
+});
diff --git a/media/less/bill.less b/media/less/bill.less
new file mode 100644
index 0000000000000000000000000000000000000000..07d0da57b17ac45c1f34eba723ada93e8483dda3
--- /dev/null
+++ b/media/less/bill.less
@@ -0,0 +1,92 @@
+@import (reference) "common.less";
+
+.RatingTableModulesHead {
+    background: #f0f7fd;
+}
+table {
+    border-top: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+    color: #363636;
+}
+
+.selectBill
+{
+    display: block;
+    width: auto;
+    margin: 0 auto;
+}
+
+select {
+    width: 32.5% !important;
+    margin-right: 1.2%;
+    &:last-child { margin-right: 0; }
+}
+
+.loadingSpinner {
+    width: 100%;
+    display: none;
+    height: 500px;
+    background: url(@IconSpinner);
+    background-repeat: no-repeat;
+    background-position: center;
+}
+
+.title {
+    padding: 5px;
+    text-align: center;
+    table-layout: fixed; /* Фиксированная ширина ячеек */
+    width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+.equal-width-cols {
+    width: 100%;
+    table-layout: fixed;
+}
+.equal-width-cols td {
+    width: 100%;
+}
+td {
+    border-left: 1px solid #ccc;
+    border-bottom: 1px solid #ccc;
+
+    vertical-align: middle;
+}
+.student_td {
+    padding: 0 5px;
+    text-align: left;
+    width: 200px !important;
+    table-layout: fixed; /* Фиксированная ширина ячеек */
+    font-size: 13px;
+    color: #417b9d;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    height: 35px;
+}
+.debt_rate
+{
+    text-align: center;
+    color: #ff0c03;
+    font-weight: bold;
+    font-size: 14px;
+}
+.success_rate
+{
+    text-align: center;
+    color: #00c509;
+    font-weight: bold;
+    font-size: 14px;
+}
+.absent_rate
+{
+    text-align: center;
+    color: #000000;
+    font-size: 14px;
+}
+.repass_rate
+{
+    text-align: center;
+    color: #cc3ac5;
+    font-weight: bold;
+    font-size: 14px;
+}
diff --git a/media/less/common.less b/media/less/common.less
index f06d90d34e6af8cfe7a3260d8aa0bd0bf2926312..73d09c95b28971a256be48a74237b88e1a16e54e 100644
--- a/media/less/common.less
+++ b/media/less/common.less
@@ -43,6 +43,7 @@
 @IconTriangleUp: "@{ImagePath}icons/triangle_up.png";
 @IconError: "@{ImagePath}/error.png";
 @IconClose: "@{ImagePath}/close.png";
+@IconSpinner: "@{ImagePath}/ajax_loader.gif";
 @IconArrowLeft: "@{ImagePath}/arrow_left.svg";
 @IconArrowRight: "@{ImagePath}/arrow_right.svg";
 @IconArrowLeftDouble: "@{ImagePath}/arrow_left_double.svg";
@@ -275,7 +276,7 @@ input[type="checkbox"] {
 // Main layer
 // ---------------------------------------------------------------------------------------------------------------------
 .main_layer {
-    max-width: 70%;
+    max-width: 85%;
     width: auto;
     min-width: 920px;
     margin: 35px auto;
diff --git a/~dev_rating/application/bootstrap.php b/~dev_rating/application/bootstrap.php
index 07ec63c0a6626fab6b63b1855af47a717a26cc31..3a174705c82eec3ab217ad088f15f872b697a063 100644
--- a/~dev_rating/application/bootstrap.php
+++ b/~dev_rating/application/bootstrap.php
@@ -176,6 +176,7 @@ Route::set('sign:restore', 'remind/<token>')
         'action'     => 'restore',
     ]);
 
+
 // --------------- Ajax features ----------------
 Route::set('handler', 'handler/<controller>(/<action>(/<id>))')
     ->defaults([
diff --git a/~dev_rating/application/classes/Controller/Handler/Discipline.php b/~dev_rating/application/classes/Controller/Handler/Discipline.php
index 0941a831ce24c2c042509a5b364a308c6fd49e78..00c3b87400baa11a0e0eb97352c8f43e73930ea0 100644
--- a/~dev_rating/application/classes/Controller/Handler/Discipline.php
+++ b/~dev_rating/application/classes/Controller/Handler/Discipline.php
@@ -33,6 +33,17 @@ class Controller_Handler_Discipline extends Controller_Handler_Api
         return ['ID' => $discipline->ID];
     }
 
+    public function action_getCompounds() {
+        $ret = '<option value="0"> -- Нет -- </option>';
+        $compounds = Model_Grades::getCompoundDisciplinesForGrade($this->post['gradeID']);
+        foreach($compounds as $cmp){
+            $cmpID = $cmp['ID'];
+            $cmpName = $cmp['Name'];
+            $ret .= "<option value = $cmpID> $cmpName </option>";
+        }
+        return $ret;
+    }
+
     # /handler/discipline/update
     public function action_update() {
         $this->user->checkAccess(User::RIGHTS_TEACHER);
diff --git a/~dev_rating/application/classes/Controller/Office/Bill.php b/~dev_rating/application/classes/Controller/Office/Bill.php
new file mode 100644
index 0000000000000000000000000000000000000000..d03880e52035ff78ebe63bef9b0b65cf01fceb54
--- /dev/null
+++ b/~dev_rating/application/classes/Controller/Office/Bill.php
@@ -0,0 +1,85 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+class Controller_Office_Bill extends Controller_Environment_Office
+{
+    public function before() {
+        parent::before();
+        Cookie::set('fD', 'true');
+    }
+
+    public static function makeTable($groupID, &$disciplines) {
+        $students = Model_Students::ofGroup($groupID);
+        $table = [];
+
+        foreach ($students as $stud) {
+            $table[$stud['ID']] = [
+                'Name'   => $stud['LastName'] . ' ' . $stud['FirstName'] . ' ' . $stud['SecondName'],
+                'grades' => []
+            ];
+        }
+
+
+        foreach ($disciplines as &$dis) {
+            $rates = Model_Rating::getRatesForStudentsGroupByStage2($dis['ID'], $groupID, 3);
+            foreach ($rates as $record) {
+
+                if (!isset($table[$record['StudentID']])) continue; // skip fake
+
+                $exam = max($record['PreviousExam'], $record['Exam'], $record['PrePreviousExam']);
+                $rate = $record['Semester'] + $record['Bonus'] + $record['Extra'] + $exam;
+                if ($rate > 100) {
+                    $rate = '100+';
+                }
+                $rateSet = isset($record['Semester']) || isset($record['Bonus'])
+                    || isset($record['Extra']);
+
+                if ($dis['CompoundDiscID']) {
+                    //Выдать "звездочку", если кто-то умудрился приписать студента к двум+ дисциплинам из одного compound
+                    if (isset($table[$record['StudentID']]['grades'][-$dis['CompoundDiscID']])) {
+                        $table[$record['StudentID']]['grades'][-$dis['CompoundDiscID']]['rating'] =
+                            (string) $table[$record['StudentID']]['grades'][-$dis['CompoundDiscID']]['rating'] . '*';
+                        continue;
+                    }
+                }
+                $table[$record['StudentID']]['grades'][($dis['CompoundDiscID'] ? -$dis['CompoundDiscID'] : $dis['ID'])] = [
+                        'rating' => $rateSet ? $rate : '–'
+                    ] + Model_Student::RecognizeDebtAndRepassing($dis, $record);
+
+            }
+            //check: student may not be attach to discipline but exist in group
+            foreach ($students as $stud) {
+                if (!isset($table[$stud['ID']]['grades'][($dis['CompoundDiscID'] ? -$dis['CompoundDiscID'] : $dis['ID'])])) {
+                    $table[$stud['ID']]['grades'][$dis['CompoundDiscID'] ? -$dis['CompoundDiscID'] : $dis['ID']] = [
+                        'rating' => '–',
+                        'debt'   => false,
+                        'repass' => false
+                    ];
+                }
+            }
+        }
+        return $table;
+    }
+
+    public static function formHead(&$disciplines) {
+        $res = [];
+        foreach ($disciplines as $dis) {
+            if ($dis['CompoundDiscID']) {
+                if (!isset($res[-$dis['CompoundDiscID']]))
+                    $res[-$dis['CompoundDiscID']] = $dis;
+            } else {
+                $res[$dis['ID']] = $dis;
+            }
+        }
+        return $res;
+    }
+
+    public function action_group() {
+        $id = $this->request->param('id');
+        $disciplines = Model_Group::with($id)->getDisciplines();
+        $this->twig->set_filename(static::OFFICE . 'bill')
+            ->set([
+                'Disciplines' => self::formHead($disciplines),
+                'Table'       => self::makeTable($id, $disciplines)
+            ]);
+    }
+}
diff --git a/~dev_rating/application/classes/Controller/Office/Groups.php b/~dev_rating/application/classes/Controller/Office/Groups.php
index 1c17100d1a3023d6619ac726e28b671a61615b28..79c4df368ba3c38c1f547596417d9d6727dc2227 100644
--- a/~dev_rating/application/classes/Controller/Office/Groups.php
+++ b/~dev_rating/application/classes/Controller/Office/Groups.php
@@ -2,5 +2,11 @@
 
 class Controller_Office_Groups extends Controller_Environment_Office
 {
-//    public function action_index() {}
+    public function action_bill()
+    {
+        $this->twig->set_filename(static::OFFICE . '/groups/bill')
+            ->set([
+                'Grades' => Model_Grades::loadAll()
+            ]);
+    }
 }
diff --git a/~dev_rating/application/classes/Model/Discipline.php b/~dev_rating/application/classes/Model/Discipline.php
index e990e09edbe23eaf0296967902061c00fdb0c3fb..38e2a39115f0a4dfed418d60a42ddd88b9ecefda 100644
--- a/~dev_rating/application/classes/Model/Discipline.php
+++ b/~dev_rating/application/classes/Model/Discipline.php
@@ -21,6 +21,8 @@
  * @property      $IsLocked    bool
  * @property      $IsBonus     bool
  * @property      $Milestone   int
+ * @property      $CompoundDiscID  int
+ * @property      $CompoundDiscName string
  */
 class Model_Discipline extends Model_Container
 {
@@ -157,6 +159,14 @@ class Model_Discipline extends Model_Container
             ])->execute()->get('Num');
     }
 
+    // Возращает текущие максимально возможные максимумы
+    public function getDisciplineRateInfo() {
+        $sql = "CALL `GetDisciplineRateInfo`(:id)";
+        return DB::query(Database::SELECT, $sql)
+            ->param(':id', $this->ID)->execute()->as_array()[0];
+    }
+
+
     /**
      * Time machine for discipline.
      * @param $stage int number from 0 to 3
@@ -174,4 +184,5 @@ class Model_Discipline extends Model_Container
                 ':milestone'  => $stage,
             ])->execute();
     }
+
 }
diff --git a/~dev_rating/application/classes/Model/Grades.php b/~dev_rating/application/classes/Model/Grades.php
index c5b7c8bf8981e617603df79ac6c3f4887bdb5147..717846a10605a84034740e2ff192c66b663d9e2f 100644
--- a/~dev_rating/application/classes/Model/Grades.php
+++ b/~dev_rating/application/classes/Model/Grades.php
@@ -11,4 +11,9 @@ class Model_Grades extends Model
     public static function loadAll() {
         return DB::query(Database::SELECT, 'CALL `GetGrades`()')->execute();
     }
+
+    public static function getCompoundDisciplinesForGrade($gradeID){
+        return DB::query(Database::SELECT, 'CALL `GetCompoundDisciplinesForGrade`(:gradeID)')
+            ->param(':gradeID', $gradeID)->execute()->as_array();
+    }
 }
diff --git a/~dev_rating/application/classes/Model/Rating.php b/~dev_rating/application/classes/Model/Rating.php
index 693c49950e06caef87d3eb6a91ea17979a4fbff6..c6c8a36fc7666cede69798b59ab023a3b335126c 100644
--- a/~dev_rating/application/classes/Model/Rating.php
+++ b/~dev_rating/application/classes/Model/Rating.php
@@ -97,6 +97,16 @@ class Model_Rating extends Model
             ])->execute();
     }
 
+    public static function getRatesForStudentsGroupByStage2($discipline, $group, $stage) {
+        $sql = "CALL `GetRatesForGroupByStage2`(:discipline, :group, :stage)";
+        return DB::query(Database::SELECT, $sql)
+            ->parameters([
+                ':discipline' => $discipline,
+                ':group'      => $group,
+                ':stage'      => $stage
+            ])->execute();
+    }
+
     public static function getFinalFormInfo($discipline, $group) {
         $sql = "CALL `getFinalFormInfo`(:discipline, :group)";
         return DB::query(Database::SELECT, $sql)
diff --git a/~dev_rating/application/classes/Model/Student.php b/~dev_rating/application/classes/Model/Student.php
index 1d418e10c877e4c7e9d8261677e94dd3c309c56e..e9a145822af3cacf500b7813988195a28d924c9f 100644
--- a/~dev_rating/application/classes/Model/Student.php
+++ b/~dev_rating/application/classes/Model/Student.php
@@ -134,4 +134,29 @@ class Model_Student extends Model_Container
 
         return $this;
     }
+
+    public static function RecognizeDebtAndRepassing(Model_Discipline $discipline, $rates) {
+        $res = [
+            'debt'   => false,
+            'repass' => false
+        ];
+        $disLimits = $discipline->getDisciplineRateInfo();
+        $semester = $rates['Semester'] + $rates['PreviousExtra'];
+        $exam     = max($rates['PrePreviousExam'], $rates['PreviousExam'], $rates['Exam']);
+
+        $semesterRatingSetted = isset($rates['Semester']) || isset($rates['PreviousExtra']);
+        $examRatingSetted = isset($rates['PrePreviousExam']) || isset($rates['PreviousExam']) ||  isset($rates['Exam']);
+
+        if ($examRatingSetted || $semesterRatingSetted){
+            $res['debt'] = ($semester + $exam) < 0.6 * $disLimits['Current'];
+        }
+
+        if ($discipline['Type'] == 'exam'){
+            if ($examRatingSetted){
+                $res['debt'] = $res['debt'] || $exam < 22;
+                $res['repass'] = (int)($rates['PrePreviousExam']) < 22 && !$res['debt'];
+            }
+        }
+        return $res;
+    }
 }
diff --git a/~dev_rating/application/routes/dean_office.php b/~dev_rating/application/routes/dean_office.php
index cf0edcf580dbd434bdd2a636566e49da65fa2da6..500925e339a04133eea828cd827f9efa10b0121c 100644
--- a/~dev_rating/application/routes/dean_office.php
+++ b/~dev_rating/application/routes/dean_office.php
@@ -1,15 +1,15 @@
 <?php
 
-Route::set('office:students:profile', 'office/<controller>/<id>(/(<action>))', ['id' => '\d+'])
+Route::set('office:common', 'office(/(<controller>(/(<action>(/(<id>))))))', ['id' => '\d+'])
     ->defaults([
         'directory' => 'Office',
-        'controller' => 'Students',
-        'action' => 'profile',
+        'controller' => 'Index',
+        'action' => 'index',
     ]);
 
-Route::set('office:common', 'office(/(<controller>(/(<action>))))')
+Route::set('office:students:profile', 'office/<controller>/<id>(/(<action>))', ['id' => '\d+'])
     ->defaults([
         'directory' => 'Office',
-        'controller' => 'Index',
-        'action' => 'index',
-    ]);
+        'controller' => 'Students',
+        'action' => 'profile',
+    ]);
\ No newline at end of file
diff --git a/~dev_rating/application/views/office/bill.twig b/~dev_rating/application/views/office/bill.twig
new file mode 100644
index 0000000000000000000000000000000000000000..ae2454d8d9b2f0bda556af827e3993a2a8c10fc9
--- /dev/null
+++ b/~dev_rating/application/views/office/bill.twig
@@ -0,0 +1,33 @@
+<table class="equal-width-cols">
+    <tbody>
+    <tr class="RatingTableModulesHead">
+        <td class="student_td title">&nbsp;</td>
+        {% for dis in Disciplines %}
+            <td class="title">
+                {% if dis.CompoundDiscID %}
+                    {{ HTML.anchor('/compound_disciplines/' ~ dis.CompoundDiscID,
+                    dis.CompoundDiscName, {'title': dis.CompoundDiscName })|raw }}
+                {% else %}
+                    {{ HTML.anchor('/rate/' ~ dis.ID, dis.SubjectName, {'title': dis.SubjectName })|raw }}
+                {% endif %}
+            </td>
+        {% endfor %}
+    </tr>
+    {% for stud in Table %}
+        <tr>
+            <td class="student_td">{{ stud.Name }} </td>
+            {% for grade in stud.grades %}
+                {% if grade.debt %}
+                    <td class="debt_rate"> {{ grade.rating }}</td>
+                {% elseif grade.rating=='–' %}
+                    <td class="absent_rate"> {{ grade.rating }}</td>
+                {% elseif grade.repass %}
+                    <td class="repass_rate"> {{ grade.rating }}</td>
+                {% else %}
+                    <td class="success_rate"> {{ grade.rating }}</td>
+                {% endif %}
+            {% endfor %}
+        </tr>
+    {% endfor %}
+    </tbody>
+</table>
\ No newline at end of file
diff --git a/~dev_rating/application/views/office/groups/!base.twig b/~dev_rating/application/views/office/groups/!base.twig
new file mode 100644
index 0000000000000000000000000000000000000000..aeeda2b9d29165d6d8ff62a6d87be7d8f47a25c4
--- /dev/null
+++ b/~dev_rating/application/views/office/groups/!base.twig
@@ -0,0 +1,11 @@
+{% extends "office/base" %}
+
+
+{% block office_tabs %}
+    <div class="tabsWrapper noTopMargin">
+        <div class="tabs">
+            <div class="tab">{{ HTML.anchor('office/groups/show',  'Просмотр')|raw }}</div>
+            <div class="tab">{{ HTML.anchor('office/groups/bill', 'Сводные ведомости')|raw }}</div>
+        </div>
+    </div>
+{% endblock %}
diff --git a/~dev_rating/application/views/office/groups/bill.twig b/~dev_rating/application/views/office/groups/bill.twig
new file mode 100644
index 0000000000000000000000000000000000000000..08c0fea9cd5d1f8c9fd0acab29bf901f5dd2bd15
--- /dev/null
+++ b/~dev_rating/application/views/office/groups/bill.twig
@@ -0,0 +1,37 @@
+{% extends "office/groups/!base" %}
+
+{% block title %}Сводная ведомость{% endblock %}
+
+{% block office_media %}
+    {{ HTML.style('static/css/bill.css')|raw }}
+    {{ HTML.script('static/js/dean/bill.js')|raw }}
+{% endblock %}
+
+{% block office_content %}
+    <h2>Сводная ведомость по группе</h2>
+    <div class="selectBill Margin10 Top Bottom">
+        <select class="defaultForm" id="semesterSelect">
+            {% for i in SemesterList %}
+                <option value="{{ i.ID }}">
+                    {{ i.Num }} семестр {{ i.Year }}/{{ i.Year + 1 }}
+                </option>
+            {% endfor %}
+        </select>
+
+        <select class="defaultForm" id="gradeSelect">
+            <option value="-1">-- Выберите курс --</option>
+            {% for Grade in Grades %}
+                <option value="{{ Grade.ID }}">
+                    {{ Grade.Num }} РєСѓСЂСЃ
+                </option>
+            {% endfor %}
+        </select>
+
+        <select class="defaultForm" id="groupSelect">
+            <option value="-1">-- Выберите группу --</option>
+        </select>
+    </div>
+
+    <div class="loadingSpinner"></div>
+    <div class="billContent"></div>
+{% endblock %}
diff --git a/~dev_rating/application/views/teacher/discipline/create.twig b/~dev_rating/application/views/teacher/discipline/create.twig
index 54aaca43660b169cb23646f5c7788d45b2d2c4ad..a8b5ab6d39fdb580074a41647ccd1f6a6c3d2406 100644
--- a/~dev_rating/application/views/teacher/discipline/create.twig
+++ b/~dev_rating/application/views/teacher/discipline/create.twig
@@ -65,6 +65,17 @@
 			</div>
 		</div>
 	</div>
+    <div class="LayerSection">
+        <div class="itemBlock">
+            <div class="title">В составе объединенной дисциплины:</div>
+            <div class="field">
+                <select class="SelectCompound defaultForm" disabled>
+                    <option value="0">-- Нет --</option>
+
+                </select>
+            </div>
+        </div>
+    </div>
 	<div class="LayerSection">
 		<div class="itemBlock">
 			<div class="title">Бонусные баллы:</div>
diff --git a/~dev_rating/static/img/ajax_loader.gif b/~dev_rating/static/img/ajax_loader.gif
new file mode 100644
index 0000000000000000000000000000000000000000..68bd58c2bbe4b49dc0787331c3488e5114c138ba
Binary files /dev/null and b/~dev_rating/static/img/ajax_loader.gif differ