(копию запостил на Т1С http://www.kuban.ru/forum_new/forum9/files/315317.html) .
Прошу помощи у сообщества...
.
на свою беду наваял в 1сине себе же простенький "пропускной пункт" - в котором идет простая регистрация простых событий входа/выхода сотрудников...
.
Регистрация пишется в плоскую таблицу из 3-х полей вида "Сотрудник, Событие, TimeStamp", например:
Петров ВХОД 20081013 11:34:15
Петров ВЫХОД 20081013 11:44:45
и т.д.
.
интервал от входа до выхода (присутствие на работе) может быть произвольно большой (например, пришел в понедельник, вышел в четверг - охрана так работает, я могу долго сидеть на работе и т.д.); интервал отстутствия на работе ("расстояние" от выхода до входа)- также может быть произвольно большой.
.
для обобщения задачи добавим условие: работа учитывается "посменно", для упрощения считаем что "рабочие" сутки начинаются в 8 часов утра текущего дня и заканчиваются в 8 часов следующего дня, "рабочие" сутки состоят из 2-х смен: дневной смены ДС (8:00-20:00) и ночной смены НС (20:00-8:00). Работник может оработать время, зацепив сразу две (три, а то и 4 смены) смены, например:
а) я пришел в 9:00 и ушел в 23:00 - на меня "учтется" в "рабочих" сутках ДС 9:00-20:00 и НС 20:00-23:00
б) я пришел в 7-30 утром и ушел 23:00 вечером того же дня: на меня учтется ДС 08:00-20:00 и НС 20:00-23:00; интервал 7:30-8:00 - зачитвается в "предыдущие" рабочие сутки.
.
требуется: составить "лист присутствия" сотрудников на работе, в котором должны быть для указанных "рабочих" суток зафиксированы время начала и окончания дневных и ночных смен; при этом входы-выходы "внутри" смены неактуальны, то есть, например:
а) я пришел в 9:00, вышел на обед в 13:00, вернулся с обеда в 14:00 и ушел с работы 21:00 - в "листе присутствия" получатся такие данные: ДС=9:00-20:00, НС=20:00-21:00
б) но если я не обедал, а выбежал на ужин в 19:30 и вернулся в 20:30, то будет в листе присутствия зафиксировано: ДС=9:00-19:30, НС=20:30-21:00
..
ПЕРВАЯ ТРИВИАЛЬНАЯ ЗАДАЧА:
Получить "лист присутсвия" для указанных "рабочих" суток для отдельно взятого сотрудника... (+ работать должен достаточно быстро - таких строчек обсчитываться может большое колво - но это условие не сильно актуально)
..
Сорри, за длительное вступление...
несмотря на кажущуюся простоту задачи - я над ней уже мозг сломал... Алгоритм есть, считает вроде правильно, но смотреть на него я без содрогания не могу... показать людям стыдно... не удовлетворяет он меня... недели через две - не "подыму"... реальный тупняк.. где-то здесь может надо схему данных или хитрый математический ход провернуть...
..
для кого-то это наверняка пройденный этап (для "зарплатников" всяких?), но я забодался реально.. ;-) спать эта задача спокойно не дает...
..
кому интересно/востребовано - подключайтесь...
..
при ломоте "работать забесплатно" - вопрос обсуждаем при качественном красивом и поянтно-читабельном алгоритме ;-)
..
Вдогонку, для поднятия интереса (в случае решения частной вышеизложенной задачи):
- интересует и общее решение, т.е. что две смены, которые закрывают сутки, что три смены, которые закрывают сутки; ясен пень, что смены идут "паравозиком", то есть разрывов между сменами - нет...
- вторую "вдагонялку" озвучу позже, когда с этой разберемся (если разберемся ;-)
..
я так думаю, что настоящие программисты быстро щелкнут эту задачку... (и хитро так по ленински прищурился)
При приходе нужна проверка, может человек уже приходил в этой смене:
Код
Если Рег.Остаток(Сотрудник, Смена,"Был")>0 Тогда
НайтиИУдалитьПрошлыйУходВЭтойСмене(); // считаем, что не уходил
СтаусВозврата(0); // и не приходил
Иначе
Рег.ДвижениеПриход(Сотрудник, Смена, 1,1);
КонецЕсли;
Показать полностью
И, наконец, при завершении смены обнуляем остатко по НаРаботе и Был по законченной смене, и переносим остатки в новую смену по сотрудникам, у которых НаРаботе=1
**Все просто.
Регистр остатков.
------------------------------
но тогда нужен документ который этот регистр двинет... на каждый вход/выход по документику ...
На вскидку, не размышляя - завести периодический реквизит в спр пользователи и по принципу выше отслеживать состояние, изменять состояние по внешнему событию. обработкой считывать инфу с реквизита за период и принимать решение. За эталон можно взять известные состояния. (табель, расписание смен с привязкой по времени)
ИМХО реквизитов может быть больше и разрядность может быть больше 2 :-)
Немного подумал....
Пример реквизит имеет вид 00000
1 разр: 0 - вышел 1-вошел
2,3 разряд 00- не работает или в отпуске или уволен, 01 график с 8.00 до 17.00 перерыв 12.00 13.00 ну и так далее 02, 03 до 99 график работы
4 разряд на обеде 1 не на обеде 0
5 разряд корректировка - для возможных смещений по некоторым графикам 0 - не допускается корректировка человек должен строго приходить и уходить для сменных графиков 1 - возможна корректировка времени типа ушел на обед в 11.50 и с обеда должен вернуться в 12.50 ну и так далее по фантазии
итого имеем на 11.00 значение 10100 на работе по графику 01
на 12.30 00110 вышел на обед
на 19.30 00100 ушел с работы
т.о. для каждого графика работы, на каждый момент времени имеем определенное значение реквизита его и сравниваем с записанными в справочник совпало - хорошо, нет в отчете видно что именно есть и что должно быть:-)
Вижу это так.
Нужно два документа:
Документ "График". Документ задает время когда работник должен находиться на работе
Документ "Пропуск". Фиксирует время прихода ухода
Таблица №1
Регистратор время Статус
Пропуск(вход) 00:00 не рабочее
График (начало) 00:15 рабочее
Пропуск(выход) 01:30 рабочее
Пропуск(вход) 02:30 рабочее
Пропуск(выход) 05:45 рабочее
График (конец) 06:00 не рабочее
По данным из таблицы №1 строим таблицу №2
Начало Конец Статус
00:00 00:15 пришел на работу заранее
00:15 01:30 работает
01:30 02:30 отсутствовал на работе
02:30 05:45 работает
05:45 06:00 преждевременный уход с работы
06:00 --:-- отдыхает
2 lefthander
А что даст периодический реквизит? Двигать без документа мы его можем только на начало дня, а на определенное время - только документом.
Задача поставлена так, чтобы мы видели события прихода и ухода. Что у нас предназначено для отражения событий? Документы.
Справочники тут как-то не очень подходит.
А хранить инфу о присутствии в регистрах выглядит красивее. и работать с ними удобнее на мой взгляд.
2 ssp_
.
некузяво...
+ вчитайтесь в сабж.
во время работы _РЕГИСТРАЦИИ СОБЫТИЙ_ - минимум вычислений! никаких блокировок, никаких документов, никаких регистров. Никаких смен/графиков. Работник может прийти и выйти в любое время.
низачот, так как описано совсем не то, что я предложил "порешать".
.
.
2 lefthander
.
вся периодика хранится в одном файле - потенциальные проблемы с блокировками.
во время регистрации - никаких графиков и смен нет! все успешно регистрируется и так.
Задача - красиво разложить решение ЗАДАННОГО в сабже.
.
.
2 Pavlovsky
не то!
мне всего-то надо:
- все время когда работник на работе = рабочее!
- нужен как раз _алгорим_ построения таблицы №2, причем таблица 2 должна В ДАННОМ Примере получится такой (при ночной смене с 0-15 до 6-00 и дневной смене с 6-00 до 0-15, ночная смена относится к предыдущему дню!)
Петров, Дневная смена: 00-00(вход) 00-15(выход) (при этом еще нужно знать, что сотрудник не входил в дневной смене раньше или вошел в предыдущую ночную смену и трудится как стахановец!!!! - из-за этого время входа в ДС - другое будет!, для упрощения примем - это первый рабочий день сотрудника.), Ночная смена 00-15 (вход), 5-45 (выход)
..
2 ssp
>Задача поставлена так, чтобы мы видели события прихода и ухода.
задача поставлена так, что нужно знать (в отчете): чтобы мы видели события прихода и ухода _В СМЕНУ_, т.е. время начала работы в смене и время окончания работы в смене...
итого: все предложения - бяка
Che не совсем понимаю в чем проблема. Поэтому может не в тему.
Если ты все же остановился на справочниках...
Время и дату события удобней регистрировать в формате похожим на DateTime. Т.е. количество секунд (или минут, если точность не важна) от какого-то дня.
Любая смена - это период между двумя датами с временем. Преобразовываем в наш формат и делаем запрос , НашеВремя > начало И НашеВремя < конец
Ну например в минутах от 1 января 2008г
Хотим посмотреть что делал наш Вася с 15 октября 22:35 по 16 октября 0:57
Начало = 288*24*60+22*60+35=415283
Конец = 289*24*60+0*60+57=416217
Пишем запрос Имя = Вася НашеВремя > 415283 НашеВремя < 416217
Получаем Все события за смену с 15 октября 22:35 по 16 октября 0:57
Вроде это должно работать быстро.
2 vovan519
да! примерно так и делаю!!!
но одним запросом не обойтись!!
за период отчета - может быть НЕ ЗАРЕГИСТРИРОВАНО НИ ОДНОГО СОБЫТИЯ!!!! но оно - есть!!!!!
пример: охрана пришла в понедельник, ушла в пятницу, отчет снимаем за среду - должно проставиться вход-выход дневная смена и вход-выход ночная смена!!!! а для этого надо _получить_ событие ДО моего периода!!!
1) Нужен механизм закрытия/открытия смены. Ищется, кто еще не вышел, ему ставится выход и в следующей смене сразу же ставится вход. Когда смена закрыта, все телодвижения сотрудников попадают в следующие смены.
2) когда сотрудник приходит, проверка был ли выход в ненезакрытой смене? Если да, то запись уничтожается, если нет, то делается новая о входе.
3) Когда сотрудник выходит, просто делается запись о выходе.
2) На каждого сотрудника маленькие таблички по текущей смене. Например, документы или дбф-файлы.
Проблемы с блокировками быть не может, т.к. один сотрудник не может одновременно совершить 2 действия и, тем более, на разных КПП.
3) При закрытии смены анализируются только "персональные" таблицы сотрудников.
Если там 2 записи, значит он ушел, их просто нужно объединить с общей таблицей.
Если 1 запись, перенести ее в сновную таблицу, добавить в основной запись о выходе концом смены, а в табличке заменить дату и время входа началом смены.
4) В случае хранения персональных таблиц во внешних файлах, алгоритм чуть-чуть модифицируется.
Перед обработкой персональных таблиц их нужно перенести в другое место, не менять 1 запись, а создавать новый файл в "штатном" месте с одной записью о входе.
Перем СобытиеПриход;
///////////////////////////////////////////////////////////////////////////////
// Функция ПолучитьВремяНачалаСмены(ГрафикСмен, НомерСмены)
//
// Параметры:
// ГрафикСмен - ТаблицаЗначений. Таблица значений, содержащая информацию о
// графике смен предприятия. Структура таблицы:
// 1. ВремяНачалаСмены - Число, время начала смены в секундах;
// 2. ВремяОкончанияСмены - Число, время окончания смены в секундах;
// Время начала и окончания n-й смены находятся в (n-1)-й строке
// НомерСмены - Число. Номер смены, время начала в секундах которой необходимо
// получить.
//
// Описание:
// Функция рассчитывает время начала смены "НомерСмены".
//
// Возвращаемое значение:
// ВремяНачалаСмены - Число, время начала смены "НомерСмены" по грфику "ГрафикСмен" в
// секундах от начала дня.
Функция ПолучитьВремяНачалаСмены(ГрафикСмен, НомерСмены)
ВремяНачалаСмены = 0;
// Алгоритм получения времени начала смены в секундах от начала дня по графику
// смен и номеру смены
Возврат ВремяНачалаСмены;
КонецФункции // ПолучитьВремяНачалаСмены(ГрафикСмен, НомерСмены)
///////////////////////////////////////////////////////////////////////////////
// Функция ПолучитьВремяОкончанияСмены(ГрафикСмен, НомерСмены)
//
// Параметры:
// ГрафикСмен - ТаблицаЗначений. Таблица значений, содержащая информацию о
// графике смен предприятия. Структура таблицы:
// 1. ВремяНачалаСмены - Число, время начала смены в секундах;
// 2. ВремяОкончанияСмены - Число, время окончания смены в секундах;
// Время начала и окончания n-й смены находятся в (n-1)-й строке;
// НомерСмены - Число. Номер смены, время окончания в секундах которой необходимо
// получить.
//
// Описание:
// Функция рассчитывает время окончания смены "НомерСмены".
//
// Возвращаемое значение:
// ВремяОкончанияСмены - Число, время окончания смены "НомерСмены" по грфику "ГрафикСмен" в
// секундах от начала дня.
Функция ПолучитьВремяОкончанияСмены(ГрафикСмен, НомерСмены)
ВремяОкончанияСмены = 0;
// Алгоритм получения времени окончания смены в секундах от начала дня по графику
// смен и номеру смены
Возврат ВремяОкончанияСмены;
КонецФункции // ПолучитьВремяОкончанияСмены(ГрафикСмен, НомерСмены)
///////////////////////////////////////////////////////////////////////////////
// Функция ПолучитьДатуСобытия(ДатаВремяСобытия)
//
// Параметры:
// ДатаВремяСобытия - Строка. Строка, содержащая информацию о дате и времени события.
//
// Описание:
// Функция получает дату из строки, содержащей информацию о дате и времени события.
//
// Возвращаемое значение:
// ДатаСобытия - Дата. Дата события.
Функция ПолучитьДатуСобытия(ДатаВремяСобытия)
ДатаСобытия = 0;
// Алгоритм получения даты события из даты-времени события
Возврат ДатаСобытия;
КонецФункции // ПолучитьДатуСобытия(ДатаВремяСобытия)
///////////////////////////////////////////////////////////////////////////////
// Функция ПолучитьВремяСобытия(ДатаВремяСобытия)
//
// Параметры:
// ДатаВремяСобытия - Строка. Строка, содержащая информацию о дате и времени события.
//
// Описание:
// Функция получает время из строки, содержащей информацию о дате и времени события.
//
// Возвращаемое значение:
// ВремяСобытия - Число. Время события в секундах от начала дня.
Функция ПолучитьВремяСобытия(ДатаВремяСобытия)
ВремяСобытия = 0;
// Алгоритм получения времени события в секундах от начала дня из даты-времени события
Возврат ВремяСобытия;
КонецФункции // ПолучитьВремяСобытия(ДатаВремяСобытия)
///////////////////////////////////////////////////////////////////////////////
// Функция ПолучитьНомерСмены(ГрафикСмен, ВремяСобытия)
//
// Параметры:
// ГрафикСмен - ТаблицаЗначений. Таблица значений, содержащая информацию о
// графике смен предприятия. Структура таблицы:
// 1. ВремяНачалаСмены - Число, время начала смены в секундах;
// 2. ВремяОкончанияСмены - Число, время окончания смены в секундах;
// Время начала и окончания n-й смены находятся в (n-1)-й строке;
// ВремяСобытия - Число. Время события в секундах от начала дня.
//
// Описание:
// Функция определяет номер смены в которую входит время события.
//
// Возвращаемое значение:
// НомерСмены - Число. Номер смены, который соответствуют времени события "ВремяСобытия".
Функция ПолучитьНомерСмены(ГрафикСмен, ВремяСобытия)
НомерСмены = 0;
// Алгоритм определения номера смены по графику смен и времени события
Возврат НомерСмены;
КонецФункции // ПолучитьНомерСмены(ГрафикСмен, ВремяСобытия)
///////////////////////////////////////////////////////////////////////////////
// Процедура ЗафиксироватьСобытиеВЛистеПрисутствия(ЛистПрисутствия, ГрафикСмен, Событие, ВремяСобытия)
//
// Параметры:
// ЛистПрисутствия - ТаблицаЗначений. Таблица значений, содержащая информацию о времени
// прихода и ухода в разрезе смен. Структура таблицы:
// 1. ВремяПрихода - Число. Время прихода на смену;
// 2. ВремяУхода - Число. Время ухода со смены;
// Время прихода и ухода на/с n-й смены находятся в (n-1)-й строке;
// ГрафикСмен - ТаблицаЗначений. Таблица значений, содержащая информацию о
// графике смен предприятия. Структура таблицы:
// 1. ВремяНачалаСмены - Число, время начала смены в секундах;
// 2. ВремяОкончанияСмены - Число, время окончания смены в секундах;
// Время начала и окончания n-й смены находятся в (n-1)-й строке;
// Событие - Строка. Вид события: "Вход", "Выход";
// ВремяСобытия - Число. Время события в секундах от начала дня.
//
// Описание:
// Процедура фиксирует события в листе присутствия.
Процедура ЗафиксироватьСобытиеВЛистеПрисутствия(ЛистПрисутствия, ГрафикСмен, Событие, ВремяСобытия)
НомерСмены = ПолучитьНомерСмены(ГрафикСмен, ВремяСобытия);
// Если фиксируемое событие это приход и ранее в эту смену приход не фиксировался
// или время этого события меньше чем время прихода в эту смену уже проставленного
// в листе присутствия, то проставляем новое время прихода в листе присутствия
// Если фиксируемое событие это уход и ранее в эту смену уход не фиксировался
// или время зафиксированного ухода меньше чем время фиксируемого события, то
// проставляем новое время ухода в листе присутствия
Если Событие = СобытиеПриход Тогда
ВремяПрихода = ЛистПрисутствия[НомерСмены - 1].ВремяПрихода;
Если ВремяПрихода = Неопределено Тогда
ЛистПрисутствия[НомерСмены - 1].ВремяПрихода = ВремяСобытия;
Иначе
Если ВремяСобытия < ВремяПрихода Тогда
ЛистПрисутствия[НомерСмены - 1].ВремяПрихода = ВремяСобытия;
КонецЕсли;
КонецЕсли;
Иначе
ВремяУхода = ЛистПрисутствия[НомерСмены - 1].ВремяУхода;
Если ВремяУхода = Неопределено Тогда
ЛистПрисутствия[НомерСмены - 1].ВремяУхода = ВремяСобытия;
Иначе
Если ВремяСобытия > ВремяУхода Тогда
ЛистПрисутствия[НомерСмены - 1].ВремяУхода = ВремяСобытия;
КонецЕсли;
КонецЕсли;
КонецЕсли; // Конец проверки вида события
КонецПроцедуры // ЗафиксироватьСобытиеВЛистеПрисутствия(ЛистПрисутствия, ГрафикСмен, Событие, ВремяСобытия)
///////////////////////////////////////////////////////////////////////////////
// Функция СформироватьЛистПрисутствия(ГрафикСмен,
// ТаблицаСобытий,
// Сотрудник,
// ДатаРабочихСуток)
//
// Параметры:
// ГрафикСмен - ТаблицаЗначений. Таблица значений, содержащая информацию о
// графике смен предприятия. Структура таблицы:
// 1. ВремяНачалаСмены - Число, время начала смены в секундах;
// 2. ВремяОкончанияСмены - Число, время окончания смены в секундах;
// Время начала и окончания n-й смены находятся в (n-1)-й строке;
// ТаблицаСобытий - ТаблицаЗначений. Таблица значений, содержащая информацию о событиях
// входа/выхода в разрезе сотрудников. Структура таблицы:
// 1. Сотрудник - Справочник.ХХХ, сотрудник по которому зафиксировано событие;
// 2. Событие - Строка, вид события "Вход", "Выход";
// 3. TimeStamp - Строка, дата и время события;
// Сотрудник - Справочник.ХХХ. Сотрудник по которому необходимо сформировать лист присутствия;
// ДатаРабочихСуток - Дата. Дата рабочих суток, по которой необходимо сформировать отчет.
//
// Описание:
// Функция формирует лист присутствия по заданному сотруднику, на указанную рабочую дату.
//
// Возвращаемое значение:
// ЛистПрисутствия - ТаблицаЗначений. Структура таблицы:
// 1. ВремяПрихода - Число. Время прихода на смену в секундах от начала дня;
// 2. ВремяУхода - Число. Время ухода со смены в секундах от начала дня.
// Время прихода и ухода на/с n-й смены находятся в (n-1)-й строке;
Функция СформироватьЛистПрисутствия(ГрафикСмен,
ТаблицаСобытий,
Сотрудник,
ДатаРабочихСуток)
ЛистПрисутствия = Новый ТаблицаЗначений;
ЛистПрисутствия.Колонки.Добавить("ВремяПрихода");
ЛистПрисутствия.Колонки.Добавить("ВремяУхода");
// Сформируем строки листа присутствия согласно количества смен в графике смен
КоличествоСмен = ГрафикСмен.Количество();
Для НомерСмены = 1 По КоличествоСмен Цикл
ЛистПрисутствияСтрока = ЛистПрисутствия.Добавить();
ЛистПрисутствияСтрока.ВремяПрихода = Неопределено;
ЛистПрисутствияСтрока.ВремяУхода = Неопределено;
КонецЦикла;
// Определим дату и время начала первой смены, а также дату и время окончания
// последней смены, для отсева событий по дате смены листа присутствия
ДатаНачалаПервойСмены = ДатаРабочихСуток;
ВремяНачалаПервойСмены = ГрафикСмен[0].ВремяНачалаСмены;
ВремяОкончанияПоследнейСмены = ГрафикСмен[КоличествоСмен - 1].ВремяОкончанияСмены;
// Если время окончания последней смены меньше времени начала первой смены, то
// считаем, что дата окончания последней смены это дата, следующая за датой
// начала первой смены иначе она равна дате начала первой смены
ДатаОкончанияПоследнейСмены = ДатаНачалаПервойСмены;
Если ВремяОкончанияПоследнейСмены < ВремяНачалаПервойСмены Тогда
ДатаОкончанияПоследнейСмены = ДатаНачалаПервойСмены + 1;
КонецЕсли;
// Зафиксируем наличие незакрытого прихода сотрудника до начала рабочих суток на
// дату "ДатаРабочихСуток", это необходимо если в течении рабочих суток сотрудником
// не производилось событие входа, т. е. он начал работу в предыдущие рабочие сутки
ДатаПоследнегоПриходаДоРабочихСутокОтчета = Дата("00010101");
ВремяПоследнегоПриходаДоРабочихСутокОтчета = 0;
ДатаПоследнегоУходаДоРабочихСутокОтчета = Дата("00010101");
ВремяПоследнегоУходаДоРабочихСутокОтчета = 0;
// Просеиваем таблицу событий по сотруднику, дате и вемени события
Для Каждого ТаблицаСобытийСтрока Из ТаблицаСобытий Цикл
СотрудникСобытия = ТаблицаСобытийСтрока.Сотрудник;
Событие = ТаблицаСобытийСтрока.Событие;
ДатаВремяСобытия = ТаблицаСобытийСтрока.TimeStamp;
Если СотрудникСобытия <> Сотрудник Тогда
Продолжить;
КонецЕсли;
ДатаСобытия = ПолучитьДатуСобытия(ДатаВремяСобытия);
ВремяСобытия = ПолучитьВремяСобытия(ДатаВремяСобытия);
// Если событие попадает в рабочие сутки по которым строится отчет, то заносим
// его в лист присутствия
Если
(
(ДатаНачалаПервойСмены = ДатаОкончанияПоследнейСмены)
И (ДатаСобытия = ДатаНачалаПервойСмены)
И (ВремяСобытия >= ВремяНачалаПервойСмены)
И (ВремяСобытия <= ВремяОкончанияПоследнейСмены)
)
Или
(
(ДатаНачалаПервойСмены <> ДатаОкончанияПоследнейСмены)
И (ДатаСобытия = ДатаНачалаПервойСмены)
И (ВремяСобытия >= ВремяНачалаПервойСмены)
)
Или
(
(ДатаНачалаПервойСмены <> ДатаОкончанияПоследнейСмены)
И (ДатаСобытия = ДатаОкончанияПоследнейСмены)
И (ВремяСобытия <= ВремяОкончанияПоследнейСмены)
)
Тогда
// Событие входит в рабочие сутки, по котрым строится отчет
// Фиксируем событие в листе присутствия
ЗафиксироватьСобытиеВЛистеПрисутствия(ЛистПрисутствия, ГрафикСмен, Событие, ВремяСобытия);
// Фиксируем последний незакрытый приход жо начала рабочих суток,
// по котрым строится отчет
ИначеЕсли
(
(ДатаСобытия = ДатаНачалаПервойСмены)
И (ВремяСобытия < ВремяНачалаПервойСмены)
)
Или
(ДатаСобытия < ДатаНачалаПервойСмены)
Тогда
Если Событие = СобытиеПриход Тогда
Если
(ДатаСобытия > ДатаПоследнегоПриходаДоРабочихСутокОтчета)
И (ВремяСобытия > ВремяПоследнегоПриходаДоРабочихСутокОтчета)
Тогда
ДатаПоследнегоПриходаДоРабочихСутокОтчета = ДатаСобытия;
ВремяПоследнегоПриходаДоРабочихСутокОтчета = ВремяСобытия;
КонецЕсли;
Иначе // Событие ухода
Если
(ДатаСобытия > ДатаПоследнегоУходаДоРабочихСутокОтчета)
И (ВремяСобытия > ВремяПоследнегоУходаДоРабочихСутокОтчета)
Тогда
ДатаПоследнегоУходаДоРабочихСутокОтчета = ДатаСобытия;
ВремяПоследнегоУходаДоРабочихСутокОтчета = ВремяСобытия;
КонецЕсли;
КонецЕсли; // Конец проверки вида события
КонецЕсли; // Конец проверки вхождения события в рабочие сутки отчета
КонецЦикла; // Конец цикла по таблице событий
// Производим обработку полученного листа присутствия:
// 1. Обрабатываем незакрытое время прихода до начала рабочих суток отчета
// 2. Посменно обрабатываем незакрытые времена прихода в течение рабочих суток отчета
// Обрабатываем незакрытое время прихода до начала рабочих суток отчета, если есть
// нехакрытое время прихода в предыдущие рабочие сутки, то время начала работы в первую
// смену равно времени начала первой смены
Если
(ДатаПоследнегоУходаДоРабочихСутокОтчета < ДатаПоследнегоПриходаДоРабочихСутокОтчета)
Или
(
(ДатаПоследнегоПриходаДоРабочихСутокОтчета = ДатаПоследнегоУходаДоРабочихСутокОтчета)
И (ВремяПоследнегоУходаДоРабочихСутокОтчета < ВремяПоследнегоПриходаДоРабочихСутокОтчета)
)
Тогда
ЛистПрисутствия[0].ВремяПрихода = ПолучитьВремяНачалаСмены(ГрафикСмен, 1);
КонецЕсли; // Конец проверки наличия незакрытого прихода до начала рабочих суток отчета
// Обрабатываем незакрытые времена прихода в течение рабочих суток:
// Если время прихода есть, а времени ухода нет, то время ухода равно времени
// окончания рабочей смены, время прихода на следующую рабочую смену равно
// времени начала следующей рабочей смены
Для НомерСмены = 1 По КоличествоСмен Цикл
ВремяПрихода = ЛистПрисутствия[НомерСмены - 1].ВремяПрихода;
ВремяУхода = ЛистПрисутствия[НомерСмены - 1].ВремяУхода;
Если
(ВремяУхода = Неопределено)
И (ВремяПрихода <> Неопределено)
Тогда
ВремяУхода = ПолучитьВремяОкончанияСмены(ГрафикСмен, НомерСмены);
ЛистПрисутствия[НомерСмены - 1].ВремяУхода = ВремяУхода;
// Если это не последняя смена, то время прихода на следующую смену
// будет равно началу этой смены
Если НомерСмены <> КоличествоСмен Тогда
ЛистПрисутствия[НомерСмены].ВремяПрихода = ПолучитьВремяНачалаСмены(ГрафикСмен, НомерСмены + 1);
КонецЕсли;
КонецЕсли; // Конец проверки наличия времени ухода при наличии времени прихода
КонецЦикла; // Конец цикла по сменам листа присутствия
Возврат ЛистПрисутствия;
КонецФункции // СформироватьЛистПрисутствия(ГрафикСмен,
// ТаблицаСобытий,
// Сотрудник,
// ДатаРабочихСуток)
Показать полностью
Для оптимизации получения незакрытого входа или неоткрытого выхода в разрезе рабочих суток можно ввести дополнительные таблички: Дата, ВремяНезакрытогоВхода и Дата, ВремяНеоткрытогоВыхода.
> во время работы _РЕГИСТРАЦИИ СОБЫТИЙ_ - минимум вычислений!
Ну так и не надо ничего вычислять при самом событии. Писать инфу куда угодно, хоть в текстовый файл. А потом, при появлении свободного момента, делать попытку провести это документом. И так пока не получится.
Че, если ты еще не отказался от справочников, то продолжим
"необходимость обращаться к событиям вне периода (как спереди периода, так и сзади) - ломает на! всю красоту... "
В справочнике "Клиент" добавить периодический реквизит "СобытиеКлиента".
В случае возникновения события:
1 как положено создаем элемент в справочнике "События"
2 кроме этого записываем этот элемент в реквизит СобытиеКлиента справочника Клиент на дату возникновения события.
В результате клиент Вася пришел 1 числа и не уходил до 5 (напился скатина и уснул).
1 числа сделали запись о событии и засунули его в пер.реквизит на 1 число.
Делаем отчет со 2 по 3. Событий нет. Смотрим в справочнике "Клиент" реквизит "СобытиеКлиента", на нач. дату нашего отчета ( т.е. 2), и там находим последнюю запись о событии приход, которую мы создали на 1 число, когда он пришел.
Все
О минусах: Как всегда работа задним числом. Хотя в момент записи периодического реквизита не трудно сделать проверку по дате и времени.
2 Altair777
> когда сотрудник приходит, проверка был ли выход в ненезакрытой смене?
я не знаю.. приходит сотрудник иди выходит ВРЕАЛЬНОСТИ! я регистрирую ОЧЕРЕДНОЕ СОБЫТИЕ = NOT.Последнее событие
(для упрощения - сотрудники дисциплинированные и всегда не забывают регистрироовать приход/уход)
2 vovan519 16.10.2008 23:16:57
> Че, если ты еще не отказался от справочников, то продолжим
..спсб! это - мысль! (надо тщательно обдумать)
проблемы "заднего числа" - НЕТ В ПРИНЦИПЕ, так как СОБЫТИЕ - ЭТО РЕАЛЬНОСТЬ В ТА ;-)
..
всем спсб!
сорри, если оператвино не отпишусь...
Абсурд!
3) Когда сотрудник выходит, просто делается запись о выходе.
Перевожу на язык программы:
Код
Если ФизическийСубъект.Действие.Выходит = Истина
Тогда СделатьзаписьВЫХОД();
КонецЕсли;
Показать полностью
Когда делается запись - неизвестно, входит сотрудник или выходит...
известно его предыдущее состояние, зарегистрированное в системе!
При этом сотрудник можер РЕАЛЬНО ВЫХОДИТЬ, но запись сделается - о входе (если он "сумел" войти мимо регистратора)
..
ТекущееСобытие = НЕ ПредыдущееСобытие;
- никакого сотрудника здесь нет, есть лишь его предыдущее состояние...
> "ТекущееСобытие = НЕ ПредыдущееСобытие"
Вот и сюда удобнее прекрутить периодический реквизит,
- не удобнее! вся периодика - хранится в одном файле! при активной работе с нескольких точек - имхо возможны потенциальные проблемы...
Сергей.
Я посмотрел внимательно на то как у меня сделана аналогичная задача в части хранения информации. Это справочник подчиненный справочнику "Сотрудники". В наименовании хранится ГГГГММДДЧЧММ. И еще один реквизит - "Направление". Он принимает значение 0 - вход на предприятие, 1 - выход с предприятия. Отчеты формируются простым циклом просмотра этого справочника в порядке наименования. Для ускорения просмотра используется аналог функции Scope (операция #30) из моих разработок (DBEng32 для Advantage 8.1/CodeBase 6.5). За неимением этой возможности (функции), думаю, придётся периодически удалять записи этого справочника потерявшие актуальность. Или сделать еще один реквизит отбора - "актуальность учетной записи" и управлять его значением, по мере необходимости, специальной утилитой. Т.е. иметь возможность "скрывать"/"открывать" старые записи.
А что еще можно придумать в 1С 7.7? Сомневаюсь.
"Красиво" в части скорости - можно значение реквизита "актуальности учетной записи" вести автоматически. Например, при формировании очередного отчета расставлять признак "устаревшей записи" в записи двухмесячной давности.
И отбор через:
ИспользоватьВладельца()
ПорядокНаименований()
ВыбратьЭлементыПоЗначениюРеквизита("СтараяЗапись",0)
будет работать быстрее.
А в части самого анализа записей - какие там могут быть красоты? При том, что в 1С 7.7 нет никакой возможности построить нормальную схему базы данных. :-((( Меня в таких задачах только и спасают мои расширения из DBEng32 (ДУСы да РУСы всякие).
Мне кажется на 7.7 нужно делать на основе справочника, который с какой нибудь периодичностью потом чистить. Сделать поля: ФизическоеЛицо, ДатаВремяВхода, ДатаВремяВыхода. При входе добавляется новый элемент с заполненым значением ДатаВремяВхода, но не заполненным значение ДатаВремяВыхода. При выходе делается запрос к справочнику с фильтром по физическому лицу и пустому значению ДатаВремяВыхода, При этом перезаписывается этот элемент и "закрывается" запись. Потом простейщим запросом можно получить отработанное время и т.п. Можно написать обработку которая закрывает период, т.е. удаляет записи о входах и выходах и формирует график отработанного времени и т.п....
Долго читал. Так до конца и не понял, в чем же все-таки проблема? Отличить выходы "покурить" от ухода с работы и разбить это по сменам, так? Если так, то я бы решал вопрос через переосмысление событий прихода и ухода с работы. Сейчас они, просто события на временной шкале. Сделай их событиями на шкале смен, и вот оно - счастье. Т.е., вводим понятие открытой смены (справочник Смены).
Поясню.
В контексте открытой смены у каждого сотрудника может быть только одно начало смены и только один конец смены (два реквизита в сотруднике). С приходом на работу у сотра начинается смена: фиксируется первый момент. Если кто-то вышел помочиться с порадного крыльца, то фиксируем, что он ушел (второй момент). Если сотр вернулся через 5 минут (1, 2 и т.д. часов, но еще в этой смене), то уход анулируется: он с работы не уходил, просто выходил помочиться. Если не вернулся в текущей смене, то все ок, ведь факт ухода остался записанным.
Но что делать, если он пришел в одну смену, а ушел в другую?
Все просто. Делаем Закрытие смены (документ / процедуру в глобальнике / обработку), которое выполняется либо автоматом, либо оператором. Закрытие смотрит, кто еще на работе, и записывает им всем по открытой смене конец смены: 20.00, а потом открывает новую смену и ставит начало новой смены: 20.00. Делает новую смену текущей.
Два реквизита времени в сотре - это данные по открытой смене. При закрытии смены их переносим в регистр по сменам. При регистрации прихода/ухода работать будет мгновенно, как это и требуется.
Таким образом, при формировании графика присутствия вообще ничего считать не нужно: тупо читаешь для конкретной смены подчиненный справочник (регистр) с данными прихода и ухода по сотрам.
_______________
Структура данных этого решения:
1. Два реквизита временни в справочнике Сотров: Начало, Конец
2. Справочник "Смены"
Реквизиты
Дата, Начало, Конец
3. Константа ТекущаяСмена, тип Справочник.Смены
Переключается закрытием смены.
4. Регистр (подчиненный справочник) "Присутствие"
Реквизиты
Сотрудник, Смена, Начало, Конец
Заполняется в моент закрытия смены
5. Обработка (процедура, документ) ЗакрытиеСмены
- Просматривает сотров, если они на работе, то проставляет им дату конца текущей смены
- Заполняет регистр "Присутствие" по данным сотров для текущей смены
- Создает новый элемент в справочнике "Смены", записывает ссылку на него в константу "ТекущаяСмена"
- Просматривает сотров, если они на работе, до проставляет им дату начала текущей смены (новой)
Воть...
_______________
Кстати, можно решить и более интересную проблему, сделать вот это неактуальным: "б) но если я не обедал, а выбежал на ужин в 19:30 и вернулся в 20:30, то будет в листе присутствия зафиксировано: ДС=9:00-19:30, НС=20:30-21:00"
Просто для этого нужно определить некоторое время Х, после которого считается, что сотр слинял, и закрытие-переключение смены делать по прошествии него. Если за это время сотр вернулся, то считаем, что он также не уходил, а выходил.
Олег.
При таком решении надо добавить в пятый пункт еще одну обработку типа "Пересчет задним числом графика работы сотрудников из-за ошибочного указания интервала времени рабочих смен".
Если при проектировании схемы базы данных можно не хранить производные (расчетные) данные, то лучше этого не делать. В данной задаче "укладка" времен присутствия сотрудника на работе в график смен легко делается в момент формирования отчетов.
За такие ошибки надо иметь операторш конкретно. По идеи, график рабочих смен дожен определяться не ими, а кем-то вышестоящим, в начале квартала (года) и заноситься заранее. Я сказал про формирование новой смены чисто абстрактно. Справочник смен уже долеж быть заполнен!
> В данной задаче "укладка" времен присутствия сотрудника на работе в график смен легко делается в момент формирования отчетов
Может быть, это - более лучшее решение, хотя я все-таки хранил бы. Будем мыслить глобально: хотим автоматизировать работу проходной промышленного комплекса с сотней-другой тысяч сотров. Укладка в момент вывода не будет слишком тормозной? Я предлагаю простое чтение регистра, а Ваш вариант - пересчеты.
"Иметь операторш конкретно" не отменяет и не заменяет вопроса исправления ошибки. И "вышестоящий" в этом вопросе не помощник. ;-)))) Просто есть такое "правило": то что можно посчитать - хранить не надо. И если этого правила придерживаться, то систему проще эксплуатировать. Да и программировать - проще. Но это дело вкуса. Посмотрим, чего скажет автор темы.
Мой вариант не пересчеты. Укладка не будет слишком тормозной т.к. количество исходных записей не больше, чем в Вашей схеме. Я тут проверил расчет своей базы - отчет по всем (100-150 человек) сотрудникам с января 2006 года с выдачей подробной информации. Меньше минуты (в локальном режиме).
Олег правильно написал, но выморочно..
Какие НА ПРОХОДНОЙ на! смены и документы? На проходной - люди ходят туда-сюда - и ВСЕ! все что может быть посчитано потом - должно быть посчитано потом - Ходжик совершенно верно заметил.
...у меня на временной оси есть события входов/выходов = это уже зарегено + в качестве "констант" есть "график смен" как вырожденный вариант : 1 смена с 00 до 24:00, как второй крайний вариант - цепочка из N смен, стыкующихся друг с другом и покрывающих "рабочие" сутки, причем "рабочие сутки" могут не совпадать с календарными сутками. все...
смотрим на рисунок: http://infostart.ru/profile/174/projects/2614/image.php?img=2079 - вот по такому "табелю" надо в отчете сделать "раскладку" по сменам...
У МНУ УЖЕ ЕСТЬ ПРОГРАММУЛИНА - но она мне не нравится!!!
Ходжик вообщем-то ближе к тому что я хочу излагает...
Итить! Ну дык повесь закрытие смены в глобальник на таймер, и о нем на проходной даже знать не будут, но твоя мудрая программа будет все закрывать магически-таинственным для всех образом сама! Вместо регистра используй подчиненный справочник, вместо документов - процедуру. Твой отчет станет формироваться элементарнейшим запросом:
Сергей.
Излагать то я излагаю, но честно сказать - не понимаю в чем загвоздка при решении данной задачи.
Собираем очередную пару событий - вход, выход. Просматриваем массив смен - сравниваем диапазоны. Разбиваем или вписываем диапазон времен событий в диапазон графика смен. И одна функция вычисления разницы между временем событий с учетом даты. Мне как то неудобно приводить исходный текст - два цикла и несколько сравнений. Чего то Вы недоговариваете...
Пришла мне глупая мысль в голову. Может быть загвоздка в самом укладывании времен прихода/ухода в график смен? Если так, то самый наглядный алгоритм это создание временного графика смен в массиве с учетом дат приход/ухода для каждой очередной полученной пары событий вход, выход. Т.е. если смены ДС (8:00-20:00) и НС (20:00-8:00), а даты прихода - 16.03.08, ухода - 18.03.08, то массив будет иметь примерно такой вид:
15.03.08 20:00 16.03.08 08:00 НС
16.03.08 08:00 16.03.08 20:00 ДС
16.03.08 20:00 17.03.08 08:00 НС
17.03.08 08:00 17.03.08 20:00 ДС
17.03.08 20:00 18.03.08 08:00 НС
18.03.08 08:00 18.03.08 20:00 ДС
18.03.08 20:00 19.03.08 08:00 НС
Имея функцию вычисление разницы двух переменных в формате ГГГГММДДЧЧСС, алгоритм, думаю, будет не сложным. Формат хранения (регистрации) входов/выхода описан выше по теме.
Или я, опять, чего-то не понял?
Или проблемма в определении конкретного момента начала/конца смены? т.е. если смена до 8:00 - то когда она кончится сегодня или завтра? относится и должна регистрироваться сегодняшним числом, а реально закончится завтра, и как точно определить, что она завтра закончится - простое сравнение начало больше конца. :)
У меня такой затык был с годами, при расчёте отопительного периода.
А насчёт предложения Планета, лучше всего исходные данные сохранять сразу, а расчётные сразу после расчёта, уже столкнулся с таким гемором(расчёт на лету), чуть где-то данные изменились и уже концов не найдёшь...
хм... не пинайте тока, а если сделать документы "Вход" и "Выход"
с проведением какого то состояния сотрудника. Провел доккумент "Вход" в результате пишем куда нить состояние "на работе" )))
2 Ходжик:
> но честно сказать - не понимаю в чем загвоздка при решении
> данной задачи.
> Собираем очередную пару событий - вход, выход.
вот-вот.. вы попробуйте соберите... причем красиво... ;-)
сабрать надо не просто "пару", а пару событий, выкину при этом промежуточные незначимые события... + например:
снимаем отчет за 21 число - за этот "рабочий" день - событий не зарегистрировано... - начинаем определять: сотрудник безвылазно сидел на работе...? (такое тоже возможно) и тогда его "работа" бкдет выглядеть так: ДС=вход в 8-00, выход в 20-00, НС=вход в 20-00, выход в 8-00... Или же имеет место ситуация когда сотрудник вышел с работы 17 числа и больше не появлялся... и т.д.
я тоже сначала думал, что в 2 цикла и пару строк кода уложусь... ;-)
Вообще, конечно надо бы внутриинфостартовские "олимпиады" проводить по таким "мелким" задачкам - все какой-то оживляж будет... ;-) а то все мегапроектами заняты...
У меня нет мегапроектов. И, похоже, уже не будет :-((( А т.к. я зануда, то привожу текст "в 2 цикла и пару строк". Разноску писать будем?
Код
Перем НачалоОтчета,КонецОтчета;
//--------------------------------------
Процедура Ошибка(КодОшибки)
Сообщить(Строка(КодОшибки),"!");
КонецПроцедуры
//--------------------------------------
Процедура Разноска(НачалоРаботы,КонецРаботы)
Если НачалоРаботы>КонецОтчета Тогда Возврат; КонецЕсли;
Если КонецРаботы<НачалоОтчета Тогда Возврат; КонецЕсли;
//Сообщить(НачалоРаботы+" "+КонецРаботы);
// Укладка в график смен
КонецПроцедуры
//--------------------------------------
Процедура Сформировать()
Люд=СоздатьОбъект("Справочник.Сотрудники");
Рег=СоздатьОбъект("Справочник.СотрудникиУчет");
Люд.ПорядокНаименований();
Люд.ВыбратьЭлементы(0);
Пока Люд.ПолучитьЭлемент()=1 Цикл
Если Люд.ПометкаУдаления()=1 Тогда Продолжить; КонецЕсли;
Если Люд.ЭтоГруппа()=1 Тогда Продолжить; КонецЕсли;
Сообщить(Люд.Наименование);
Нач=""; Кон="";
Рег.ПорядокНаименований();
Рег.ИспользоватьВладельца(Люд.ТекущийЭлемент());
Рег.ВыбратьЭлементы();
Пока Рег.ПолучитьЭлемент()=1 Цикл
Если Рег.ПометкаУдаления()=1 Тогда Продолжить; КонецЕсли;
Если ПустоеЗначение(Рег.Наименование)=1 Тогда Ошибка(1); Продолжить; КонецЕсли;
Если Рег.Направление=0 Тогда // Вход
Если ПустоеЗначение(Нач)=0 Тогда Ошибка(2); КонецЕсли;
Если ПустоеЗначение(Кон)=0 Тогда Ошибка(3); КонецЕсли;
Если Рег.Наименование>КонецОтчета Тогда Прервать; КонецЕсли;
Нач=Рег.Наименование; Кон="";
Продолжить;
Иначе // Выход
Если ПустоеЗначение(Нач)=1 Тогда Ошибка(4); Продолжить; КонецЕсли;
Если ПустоеЗначение(Кон)=0 Тогда Ошибка(5); КонецЕсли;
Кон=Рег.Наименование;
КонецЕсли;
Разноска(Нач,Кон);
Нач=""; Кон="";
КонецЦикла;
Если (ПустоеЗначение(Нач)=0) И (ПустоеЗначение(Кон)=1) Тогда // Еше на работе
Кон=Формат(ТекущаяДата(),"ДГГГГММДД")+Сред(СтрЗаменить(ТекущееВремя(),":",""),1,4);
Разноска(Нач,Кон);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
//--------------------------------------
НачалоОтчета="200803160000";
КонецОтчета= "200803182400";
(AVARY)
С моей стороны представлена "реализация" потому, что алгоритм представлен выше по теме. И мы не сошлись в мнениях - "два цикла и два оператора" требуется для реализации моего алгоритма или больше ;-)))
Сергей.
Ошибки возможно есть, т.к. мне пришлось вытаскивать эту логику из моей работающей системы - убирал все лишнее для наглядности представления самой сути. Но вся суть - здесь "в двух циклах и двух операторах".
Укладки у меня в промышленной системе нет - надо писать с нуля. Но если взять идею из сообщения от 21.10.2008 02:29:30 (Блин! Почему нет номеров сообщений в форуме и дат в "прямом эфире"?), то это тоже два цикла. Но уже три оператора. Правда, более простых. Напишу, если Вы признаете первую часть верной. ;-)))