воскресенье, 28 марта 2010 г.

TBPD: HelpDesk

Так, с наворотами в HelpDesk в виде цепочки задач я немного психанул...
* там нет много задач. Там одна задача. Которую надо как-то порешать.
* состояния открыто/закрыто - мало. Схема new-accepted-done-submited - лучше. Это тоже, что и Opened/Close, но используя простейшее квитирование (подтверждение приема). Open/Close годится для Google Task - когда сам себе автор и исполнитель. Где участвует двое - там нужно подтверждение, что до человека дошло.
* Другой вопрос - ведение журнала изменения состояния задачи. Это - да, вещь. Пока можно ограничиться заливкой в журнал Django - с каментами - что произошло.
Итого - схема такая:
* Задача - одна.
* Состояний - 4 (New, Accepted, Completed, Approved) - отдельная модель.
* Переходов - 8:
** Create ( None => New)
** Route ( New/Accepted => New )
** Invalid/Duplicated ( New => Completed )
** Accept ( New => Accepted )
** MkDep ( Accepted => Accepted ) - родить подзадачу / зацепиться за существующую
** Done ( Accepted => Completed )
** ReOpen ( Completed/Approved => New )
** Approve ( Completed => Approved )
* Форм - 2 (список (с фильтром) / задача (с историей))
* ведется журнал в системном журнале Django:
** объект - эта задача
** время - автомат
** user - понятно
** каменты: действие (переход из - в), каменты (юзеровские)


ЗЫ: Не, нифига. Сливать задачу Из New в Invalid - не надо. Человек нажаль педаль Просмотреть на задаче - всё, он её прочитал - значит - Принял (Accept). А потом уже пусть делает с ней всё, что хочет - перенаправляет, сливает, жалуется - пофик. Он её _прочитал_ => принял к сведению. Типа подтверждения прочтения. Чтобы вариантов "а я еще не заглядывал в список - компьютер не работал" не было.

понедельник, 8 марта 2010 г.

TBPD: Tasks

С контактами (организациями и людьми) разобрались - там хоть и навороченно, но вопросов пока нет концептуальных.
Переходим к Задачам.
Экспериментировать будем на HelpDesk. За пример возьмем opentodo и bugzilla - с доработкой напильником под себя.
(Следующей кошкой можно взять классику - Заявка/Счет/Оплата/Доверка/Накладная|Акт+СФ/Оприход. Тут уже интереснее - масса ветвлений и неожиданных поворотов - зато всё определено (а на этой базе можно сделать данный документооборот - с формами, но без бухучета... Кстати - идея! :-)).
Общие принципы:
* все объекты (в т.ч. задачи) наследуют одного предка;
* все специфичные задачи наследуют общий класс Задача - с доработкой по месту;
прохождение задачей своего жизненного пути - это цепь задач, только одна из которых (последняя в цпочке) может быть активной. Остальные уходят в архив - для истории;
* Задача может быть только открытой и закрытой;
* Закрыться задача может только успешно - или родив следующую задачу;
Задача может включать другие задачи, только когда все подзадачи завершены (условия и/или - потом);
* Задачу можно заблокировать успешным выполнением другой задачи (то, что в bugzilla назвается блокирующим багом). Это - не подзадача, а совсем из другого потока.
Итак - пора рисовать классы (на пальцах):
GwObject
Предок всех остальных классов. Gw - от GroupWare. Здесь прошиты возможности наследования, группировки и ассоциаций - всё по-взрослому. Все поля (кроме type) - необязательные:
* type:GwOType - тип наследника (расписыват не буду - просто enum); Обязательно прописывается наследниками.
* ancestor:GwObject - предок - объект, с которого тянутся поля, которые в данном объекте не прописаны (пример: девушку угораздило выйти замуж; тогда всё, кроме фамилии, она может наследовать от своей предыдуще жизни);
* group:GwObjects[] - массив подчиненных объектов. Хотя в первой версии лучше сделать поле master:GwObject - объект, которому подчинен данный. В последнем случае 1 объект может входить только в одну группу, но на безрыбье пока хватит.
* links:GwObjects[] - связи, просто связи. Связи образуют некие группы, один объект может участвовать в нескольких группах связей. Реализация тривиальна, расписывать не буду.
Методы:
* delete() - здесь объект должен поприбивать всех подчиненных (это будет сделано автоматом) и связи (аналогично).
* addson() - (решил сюда перенести, раз уж группы - здесь) - добавить подобъект (того же типа (?)).
* delson() - хез, может надо...
Task
Задача в самом общем виде. В принципе, её можно сразу и сажать в некий tasklist. Все поля (почти) тоже необязательны.
* subtype:TaskType - здесь, например, HdTask (задача HelpDesk'а) указывает свой подтип.
* author - понятно
* assignee - понятно
* created - обязательно, автомат
* deadline - понятно
* subject - обязательно
* description - понятно
* completed:bool - True - задача завершена
* result:enum(Null/ok/forward) - если задача завершена, то поле должно быть установлено - в ok, если всё чики, или в forward - если неуспешно и управление перешло следующей задаче. Вариант #2 - этого поля нет, а поле prev заменить на необязательное next - так будет лучше с т.з. неизбыточности даннных.
* prev:Task - какой задаче перешло управление по завершению данной. Обе задачи должны иметь один subtype (?). Это поле - реализация последовательности задач - об чем, собсно, и гениальность идеи - реализация бизнес-процессов как групп/последовательностей задач. По существу - однонаправленный список (хотя, может, лучше сделать двунаправленный?..)
Методы:
* delete() - здесь, ко всему прочему, надо "выкусить" себя из последовательности.
* complete() - завершить успешно. Мы не можем завершить задачу, пока не завершены успешно все подзадачи.
* forward() - завершить эту задачу, родить новую, передать ей всё, что надо - и сообщить папе (master'у группы), что мы уже не играем, мы в домике (архиве) - а за нас играет следующая задача (вот её окончания и надо ждать).
HdTask
Собсно, задача для HelpDesk. Все поля - необязательные.
* project:Project - ну или там тема, категория... Для фильтра.
* state:enum - состояние текущей задачи - в соответствии с диаграммой состояний.
* comments:txt[] - каменты.
* files:File[] - файлы.
Все поля можно указать только в главной задаче, в подзадачах - или не указывать совсем, или наследовать - кроме state (пока не решил).
Диаграммы состояний opentodo и bugzilla:
В принципе, если выкинуть справа состояние Unconfirmed (которое нужно только в public-трекерах), то диаграммы очень похожи. Ну, разве что, слева - более упрощена. Кроме того, добавим action "поцепить зависимость от другой задачи" (есть прецеденты).
Вот теперь будет use cases на пальцах.
* Юзверь создает задачу. Создается HdTask и сразу же - его первая (если не будет аварии - единственная) подзадача того же типа HdTask. Мегазадача будет ждать, пока текущая подзадача не завершится успешно. Пусть юзверь не назначил исполнителя (блондинко, куле...), подзадача будет со статусом Unassigned.
* Сисодмин видит список Unassigned-задач, назначает исполнителя. Этим он завершает задачу-сына, но не успешно, а forward: эта подзадача закрывается, открывается новая, в старой прописывается поле next, мастер-задаче вместо первой подзадачи подсовывается новая, старая уже не играет и уходит в историю. Теперь мастер-задача ждет окончания второй подзадачи. Новая задача имеет статус Assigned. Можно браться за работу.
* Исполнитель может обнаружить, что одна задача требует массу телодвижений. Не бидэ - жмем педаль "Подзадачи" - и текущая подзадача (2-го уровня) рождает несколько подзадач (уже третьего) - и будет нежно ждать успешного завершения всех из них.
* Или же исполнитель обнаружил, что ему сказали налить ведро воды, а он вчера сказал секретутке купить ведро (оракул потому что). Тогда он может повесить свою задачу в DenendsOn задаче, которую дали ему. Это разные задачи и не из одной группы - но факт имеет место быть. Тут пока плавает, на первом этапе можно похерить (возможно, лучше сделать вхождение объекта во много групп).
* Ну, пуст исполнитель тупо выполнил задачу Assigned. Жмем педаль, задача закрывается, пойвляется новая - с состоянием Resolved (Master-задаче статус не даем - она тупо ждет завершения всех подзадач - это для тех мастер задач, которые запускают сразу несколько подзадач).
* Автор проверят результат, жмет "Одобряю". Состояние новой подзадачи становится - Verified.
* Исполнитель жмет "Close". Подзадача завершается успешно. Можно вручную или или же автоматом закрыть и основную задачу. Тема закрыта.
Слайды

четверг, 4 марта 2010 г.

Addresses


Начал рисовать МегаСуперБазу классов. Коих аж ДВА - 1. Контакты, 2. Задачи.
А больше ничего интересного пока нед.
В контактах вроде всё разрулил, дошедши до самого интересного - адреса.
Скачал КЛАДР - почитал - долго думал...
Резюм: а) ребята проделали охренительно огромную работу. Само ТЗ - высший пилотаж. После того, как прочитамши, что может быть адрес населенный_пункт-дом или там область-какой-то_километр_жд-будка - выпал в астрал...
б) но реализация... все эти DBF-выкрутасы с индексами в 2010-м году выглядят в лучшем случае странно...

Итого принято решение - что будем делать с Адресом:
1. адреса будут в отдельной модели, у Контактов будет сцылки на адреса.
2. база адресов - _своя_. И представляет собой одну-единственную рекурсивную таблицу.
3. но - в базе указываются "мягкие" (зависящие от страны - типо "KLADR:123456789000") ссылки на местные справочники, по которым можно проверить правильность адреса.

Слайды:

среда, 24 февраля 2010 г.

TBPD: Tasks-based Business-Processes Definition

Ну, в общем, тут, видимо, этой штуке и место.
Общая идея в том, чтобы автоматизировать бизнес-процессы не на базе Activities, а на базе Задач.
Итак:

Суть:

  • Все бизнес-процессы (т.е. последовательности действий) есть последовательности Задач: одно действие - один тип Задач;
  • Задача может зависеть от других Задач - как начало Задачи, так и её завершение;
  • Задача не может быть удалена или изменена - только завершена с тем или иным исходом;
  • Задача может "включать" другие Задачи путем установления зависимостей. Вариант "включения" - когда подзадача активируется при активации надзадачи, а надзадача может быть завершена при завершении подзадачи.

Особенности:

  • Любая Задача имеет как минимум автора, дату/время создания, исполнителя, дату/время прочтения задачи исполнителем; дату/время завершения (для завершенных);
  • Задача может быть только в четырех состояниях: неактивная, активная непрочитанная, активная прочитанная (автомат), выполненная (с тем или иным исходом);
  • Состояние задачи может зависеть от состояния других Задач:
    • Задача может быть неактивной до тех пор, пока не будут завершены с нужным исходом другие Задачи, от которых зависит эта задача;
    • Активная задача не может быть завершена с определенным исходом, если не завершены с нужным исходом Задачи, от которых она зависит;
  • Выполнение задачи может создавать другие задачи (какие - может зависеть от исхода);
  • Самая первая задача в бизнес-процессе может быть создана Событием - специальный тип Задачи без задачи-родителя;
  • Завершение Задачи может породить другую Задачу того же типа; тогда в работе участвует только последняя задача в такой цепочке - получается "история выполнения задачи";
  • С Задачами можно работать в любом ПИМе; некоторые можно там же завершать, если ПИМ поддерживает нужные исходы.

Примеры:

  • Зависимость неактивности: "Оплатить счет" неактивна, пока не выполнено "Получить счет";
  • Зависимость завершения: "Оплатить счет" не может быть завершена успешно, пока не выполнена задача "Получить выписку из банка" - тоже успешно;
  • Группировка: "Купить товар" не может быть завершена, пока не завершены "Оплатить товар" и "Получить товар"; "Оплатить товар" не появляется, пока не появится "Купить товар".
  • Зависимость следующих задач от исхода предыдущей: завершение "Оплатить счет" создает "Получить товар" (если счет оплачен успешно) - или же "Купить за нал", если не оплачен по причине денег на счету ёк;
  • Задача порождает задачу того же типа: изменение сроков выполнения задачи, замена исполнителя.

Отличия от классики:

  • Нет "состояния" процесса или его части (начал выполнять, выполнил наполовину и т.д.) - есть только Задачи - выполненные и не выполненные. Т.е. "состояние" процесса - это суперпозиция состояний всех его задач.
  • Нет многих состояний Задачи (начал выполнять, почти закончил выполнять и т.д.) - только выполнена или нет (неактивна/активна, прочитана/не прочитана - это уже просто техника, юзер оперирует только "открыта" и "закрыта").
  • Самое главное: контролируется не процесс, а результат.

Ключевые преимущества:

  • динамическое планирование проекта - схема выполнения проекта может меняться на лету без дополнительных усилий - просто в силу внешних обстоятельств и результатов решения промежуточных задач;
  • внятное визуальное представление хода выполнения проекта - для руководства (цветной граф задач и их связей - цвет задачи/связи зависит от успешности и сроков её выполнения);
  • простое и удобное представление для исполнителей (простой список задач с возможностью группировки/сортировки/фильтров);

Идеи:

  • задачи обеспечивают/требуют (Requires/Provides) некие "сервисы" (на самом деле - события). Триггеры вешаются на эти сервисы. Задачи включают/выключают сервисы. Это более удобно, чем вешать триггеры пачки задач на пачки друггих задач (которые сегодня есть - завтра нет).
  • Задачи завершаются всегда - или исполнителем, или системой по дедлайну (с генерацией следующих задач - для принятия решения).
  • Возможно, если задача не меняется в течении всего своего цикла жизни (а это уже не задача, а бизнес-процесс) - то хранить только один экземпляр и вести историю изменений.

среда, 3 февраля 2010 г.

Пробуем raw http

Идея была такая - а что, если python-gdata делает, больше, чем просят? В смысле - запрашивает.
План работы:
1. написать приложение, которое, пользуясь _чистым_ http сделает те же запросы (ну и вообще полезно для отладки).
2. сравнить результаты.
Сделал. Python+httplib+urllib. По дороге научился получать у гугля ключи от хаты (токен авторизации) - классная вещь.
Результаты: не отличаются НИЧЕМ :-(
gdata отдал 20KB (оформлено - с ns и всё такое), raw http - 19KB неоформленного xml.
Информация - та же.
Вывод - чудо не произошло.
Остался последний патрон: Atom feed history spec (RFC 5005).
ЗЫ: ну не может быть чуда нед! Гугль даже пишет журнал _всех_ запросов! О чем прямо сказано в Google API - "указывайте приложение, каким просите - надо для истории".

воскресенье, 24 января 2010 г.

GooglePIM alive

Ну вот...
Замучила совесть вконец - человеку ж обещал поработать надо темой - а сам закинул, цуко... Не от хорошей жизни, а от работы - но тем не менее...
Сегодня бортанул все халтуры и немного поработал с GData.
Вопрос был простой: как-то обеспечить синхронизацию локальных данных с гуглем.
Для этого надо знать - что с последнего запроса:
1. удалено
2. добавлено
3. изменено
Ну, с 3) проблем больших нет - нам бы только узнать, что изменилось, а подробности уже получим, когда наступим на это дело. GData такую информацию отдает (в смысле - можно попросить прислать "updated с...").
С 2) тоже не проблема - как 3), только "published с ...".
А вот с 1) - полная засада. Т.е. гугль просто не скажет, что удалено. Удаляется с концами. Единственное исключение - Контакты - они помечаются deleted и живут так ровно 1 мес (если спецом не удалить вообще).

Итого родился алгоритм - спросить гугля пачкой сразу:
1. спросить - сколько элементов вообще (эта цифра ничего не значит еще).
2. спросить - сколько появилось новых (вот в этом месте мы уже будем знать - сколько удалено; хотя, к сожалению, не будем знать - кто - придется шерстить всех, цуко!).
3. и кто изменился.

Для этого сегодня был проведен эксперимент: сколько и каких данных отдаст гугль по простейшим запросам типа "кагдила".
Тестировал на своем блогспоте (справка - блогспотом я называю точку входа в блоггер, откуда можно узнать линки на все свои блоги (у меня их - 12), оттуда - на посты, оттуда - на каменты; т.е. блогспот - это корень блоггера (для данного юзверя)).

Есть 2 новости - плохая и как всегда.
Плохая - если гуглю ничего не сказать, то по запросу к блогспоту он отдаст:
1. кол-во блогов (это хорошо)
2. updated будет равно времени запроса (это плохо) - а не времени updated хоть одного блога (или времени удаления... короче - времени изменения состояния блогспота);
3. и, цуко, все блоги - с id, линками, названиями, тегами и прочей муйней.
Итого 12 блогов мне обошлись в 20KB. Не бог весть что - но тоже ж жалко... Это только блоги. Контактов, я думаю, отдаст на все 100 кил. Один пук - и 100 кил коту под хвост. С мобилы, ога...

Хорошая новость в том, что можно попросить ?max_results=0 - и гугль отдаст только голову - кол-во блогов (если попросить - то кол-во богов, соответсвующих критерию (updated, published).
Получилось 1KB. Тоже не фонтан - но не 20 же ж!

Теперь - к следующему уроку приготовить:
* попробовать получить блогспот не через python-gdata (может это он гонит - слишком заносит хвост клеенту и берет то, что не просили), а напрямую по atom (только надо разобраться с авторизацией - гугль же хрен отдаст просто так - за что и уважаю).
* Попробовать сделать batch-запрос - итого+published+updated.
* попросить Деда Мороза прислать мне инфу - как попросить гугль отдать только entry id вместо всего entry. Тогда я бы просто получил список id обновленных и новых entries, пометил в своей базе, что они - несвежие, а разбирался бы потом. А самое главное - сразу бы удалил несуществующие.

Еще надо бы внимательнее почитать http://code.google.com/p/libgcal/ - там есть упоминание о некоем fastsync (хотя в чЮдеса не верится).

PS. Но вообще прямо в руки лезет идея промежуточного сервера, который всю эту мороку с rss возмет на себя - а мне отдаст в чистом виде.