Мастер класс «O-Planet»: Произвольный отчет с группировками.

Опубликовал Олег Пономаренко (O-Planet) в раздел Программирование - Практика программирования

Помнится, как-то давно обещался я опубликовать что-то вроде авторской методики написания отчетов с группировками...
Помнится, как-то давно обещался я опубликовать что-то вроде авторской методики написания отчетов с группировками. Не знаю, насколько она авторская, но я дошел до нее опытным путем, пробуя разные варианты. Как правило, в последние дни перед сдачей проекта, когда ты устал, и делать нечто изысканное просто нету сил, а остается набросать еще несколько десятков разных отчетов, по которым, собственно, и будут оценивать твою программу, тогда, возможно, эти мои несложные советы и пригодятся…

Как раз очень кстати у меня есть заказ по написанию такого отчета, который нужен к завтраму. В общих словах, имеется модифицированная программа «1С:Бухгалтерия 7.7», в которой ведется учет расхода материалов в мебельном производстве. Директор пожелал видеть этот расход в разрезе групп материалов, заказчиков и дней выпуска. Самое смешное, что выпущенная продукция его почему-то не интересует. Они ее вообще обобщают: мебель, резка, еще что-то. Впрочем, какая разница, что у меня за задача! Сейчас мы обсуждаем универсальную технологию быстрого написания отчета с произвольным количеством группировок. Поэтому, поехали!

1. Суть метода

Определяем одну или несколько базовых группировок и делаем самый подробный запрос по ним. Чаще всего, базовой группировкой, из которой можно все достать, будет документ и (или) строка документа. Данные запроса выгружаем в таблицу. Для каждого уровня группировок заводим новую таблицу, выгружаем в нее данные из основной, сворачиваем так, как нам надо, сортируем. Строим отчет простым обходом базовой таблицы, отслеживая изменения значения полей группировок, и, когда нужно, выводим итоговые секции.


Возможно, метод не самый оптимальный, но достаточно простой, универсальный и быстрый.

2. Создаем форму отчета.

Это просто. Чтобы особо не возиться, можно взять «кусочки» из отчетов «1С:Торговля и склад 7.7». На форму отчета устанавливаем выбор периода, поля для фиксированных значений отдельных группировок и список самих группировок. Функции кнопок сдвига списка также забираем из типового отчета, если писать их лень.

Не забываем прописать начальные установки дат и, возможно, каких-то значений в «ПриОткрытии». Также стоит заполнить таблицу группировок. Здесь надо исходить из того, что значение группировки в списке будет совпадать с именем переменной, соответствующей нашей группировки, в запросе и таблицах. Также необходимо связать группировки с параметрами для отбора по значениям на форме: имя группировки должно соответствовать имени параметра с префиксом «Выб». Например: «Товар» - «ВыбТовар». Лепить параметры отбора для всех группировок, разумеется, не стоит.

В моем случае список будет выглядеть так:

Группировки.ДобавитьЗначение("Группа","Группа материалов");
Группировки.ДобавитьЗначение("Материал","Материал");
Группировки.ДобавитьЗначение("Контрагент","Заказчик");
Группировки.ДобавитьЗначение("ДатаДок","Дата");
Группировки.ДобавитьЗначение("ТекущийДокумент","Документ"); 


3. Строим основной запрос в процедуре «Сформировать»

Для этого лучше воспользоваться конструктором. Особо мудрить не стоит. Конструктор поможет избежать рутины, но определять все с его помощью было бы неправильно, потому что реквизиты запроса и, условия зависят, в общем-то, оттого, что было выбрано и пользователем отмечено в форм, и от вида группировок запроса. Мы их доопредилим позже.

А пока у меня получилось следующее:

"//{{ЗАПРОС(Сформировать)
|Период с ДатаНачала по ДатаКонца;
|Обрабатывать НеПомеченныеНаУдаление;
|ТекущийДокумент = Документ.ТребованиеНакладная.ТекущийДокумент;
|ДатаДок = Документ.ТребованиеНакладная.ДатаДок;
|Материал = Документ.ТребованиеНакладная.Материал;
|Группа = Документ.ТребованиеНакладная.Материал.Родитель;
|Цена = Документ.ТребованиеНакладная.Цена;
|Количество = Документ.ТребованиеНакладная.КоличествоОтпущено;
|ВидПеремещения = Документ.ТребованиеНакладная.ВидПеремещения;
|Контрагент = Документ.ТребованиеНакладная.Накладная.Контрагент;
|Группировка ТекущийДокумент;
|Группировка СтрокаДокумента;
|Условие(ВидПеремещения = 0);
|"//}}ЗАПРОС


Мне вообще крупно повезло: даже не приходится возиться с бухгалтерскими итогами (кстати, по настоянию бухши!) У них в программе цены в строках документов соответствуют средним суммам по проводкам, и в накладной по списанию материалов хранится ссылка на реализацию. (Сам делал так, самому смешно!)

Текст запроса присвоим одноименной переменной. Также нам понадобится пустая строка СтрШапки.

4. Добавляем в запрос условия отбора.

Нам понадобится процедурка типа такой:

Процедура ДобавитьУсловие(ТекстЗапроса,Рекв,Имя,Слово,СтрШапки=0,Знак="=")
ТекстЗапроса=ТекстЗапроса+"Условие("+Имя+Знак+"Выб"+Имя+");";
Если СтрШапки<>0 Тогда
  СтрШапки=СтрШапки+?(СтрШапки="","",РазделительСтрок)+
    "Отбор по "+Слово+": """+СокрЛП(Рекв.Наименование)+"""";
КонецЕсли;
КонецПроцедуры // ДобавитьУсловие() 


Она позволяем добавить в текст запроса проверку условия и одновременно формирует строку выбранных условий, которую потом мы поместим в шапку нашего отчета.

Помести под определением первичного текста нашего запроса проверку выбранных условий по типу:

Если ВыбИМЯ.Выбран()=1 Тогда
ДобавитьУсловие(ТекстЗапроса,Выб … ,"ИМЯ","РАСШИФРОВКА",СтрШапки);
КонецЕсли;

В моем случае

СтрШапки="";
Если ВыбМатериал.Выбран()=1 Тогда  
  Если ВыбМатериал.ЭтоГруппа()=1 Тогда 
    ВыбГруппа=ВыбМатериал;
    ДобавитьУсловие(ТекстЗапроса,ВыбГруппа,"Группа","группе материалов",СтрШапки);
  Иначе	
    ДобавитьУсловие(ТекстЗапроса,ВыбМатериал,"Материал","материалу",СтрШапки);
  КонецЕсли;
КонецЕсли;
Если ВыбКонтрагент.Выбран()=1 Тогда  
  ДобавитьУсловие(ТекстЗапроса,ВыбКонтрагент,"Контрагент","контрагенту",СтрШапки);
КонецЕсли;


5. Формируем базовую таблицу

Вставляем вполне стандартный кусок, не забыв прежде определить переменную Т:

З=СоздатьОбъект("Запрос");
Если З.Выполнить(ТекстЗапроса)=0 Тогда
Сообщить("Какая-то ошибка в запросе!");
Возврат;
КонецЕсли;
З.Выгрузить(Т,1,0);

Вот теперь стоит вспомнить, каких группировок не было в нашем запросе по каким-либо причинам и добавить их сейчас в таблицу. Делать это просто:

Если Помечен("ИМЯ_ГРУППИРОВКИ")=1 Тогда
Т.НоваяКолонка("ИМЯ_ГРУППИРОВКИ");
КонецЕсли;

Разумеется, эти новые колонки надо заполнить в цикле.

Функция «Помечен» должна быть определена выше. Она проверяет, была ли помечена в запросе та или иная группировка:

Функция Помечен(Имя)  
  Для К=1 по Группировки.РазмерСписка() Цикл
    ИмяГр=Группировки.ПолучитьЗначение(К);
    Если ИмяГр=Имя Тогда
      Возврат Группировки.Пометка(К);
    КонецЕсли;
  КонецЦикла;	
  Возврат 0;
КонецФункции // Помечен() 


В моем запросе не была определена одна переменная: «Сумма». Но это не группировка, поэтому я ее определяю без проверки:

Т.НоваяКолонка("Сумма","Число",15,2);
Т.ВыбратьСтроки();
Пока Т.ПолучитьСтроку()=1 Цикл
  Т.Сумма=Т.Цена*Т.Количество;
КонецЦикла;	


6. Формируем таблицы по выбранным пользователем группировкам

Еще раз поясню, что это такое. Представим, что в моем примере имеется отмеченными всего две группировки: «Заказчик» и «Материал». Тогда печатная форма отчета должна состоять из итоговых строк по заказчикам в разрезе материалов. Для этого мне потребуется всего две таблицы. Первая – это базовая таблица, сформированная по запросу, а вторая – таблица, построенная на основе базовой, данные в которой (сумма и количество израсходованных материалов) свернуты по заказчикам. Соответственно, при построении отчета, когда совершается обход строк основной таблицы, если в текущей строке заказчик изменился, то это означает, что необходимо вывести результирующую строку по новому заказчику и продолжать обход.

Нам потребуется массив размером, соответствующим размеру списка группировок.

Посчитаем число отмеченных группировок:

ГрВсего=0;
Для К=1 По Группировки.РазмерСписка() Цикл
  Если Группировки.Пометка(К)=0 Тогда
    Продолжить;
  КонецЕсли;   
  ГрВсего=ГрВсего+1;
КонецЦикла;	


Также нам необходима строка «СуммыГруппировок» для свертки таблиц и список выбранных пользователем группировок на форме отчета.

Формировать результирующие таблицы по группировкам будем в цикле. Сформированные таблицы поместим в архив.

СуммыГруппировки="Сумма,Количество";
Группируем="";
Сортируем="";
СписокГруппировок=СоздатьОбъект("СписокЗначений");
Л=0;
Для К=1 По Группировки.РазмерСписка() Цикл
  Если Группировки.Пометка(К)=0 Тогда
    Продолжить;
  КонецЕсли;   
  Л=Л+1;
  Зн=Группировки.ПолучитьЗначение(К,Стр);
  СписокГруппировок.ДобавитьЗначение(Зн);
  Заголов=Заголов+?(Заголов="",""," / ")+Стр;
  Группируем=Группируем+?(Группируем="","",",")+Зн;
  Сортируем=Сортируем+?(Сортируем="","+",",+")+Зн;
  Если Л<ГрВсего Тогда
    Арх[Л]=СоздатьОбъект("ТаблицаЗначений");
    Т.Выгрузить(Арх[Л],,,Группируем+","+СуммыГруппировки);
    Арх[Л].Свернуть(Группируем,СуммыГруппировки);
    Арх[Л].Сортировать(Сортируем);
  КонецЕсли;                                       
КонецЦикла;


Как видите, таблицы по группировкам спокойненько лежат в массиве, отсортированные как надо, ожидая своего часа.

Кстати, не забудем теперь сгруппировать и отсортировать саму базовую таблицу.

Т.Свернуть(Группируем,СуммыГруппировки);
Т.Сортировать(Сортируем);


Мы готовы к заключительному этапу.

7. Рисуем печатную форму.

При рисовании печатной форму стоит помнить, что пользователь должен все-таки тоже что-то увидеть, если он по глупости отметил сразу все возможные группировки. Для этого стоит выделять цветом или отступом итоговые строки.

В моем варианте шаблон таблицы выглядит вот так:



Особо внимательные, думаю, заметили, где используются сформированные ранее строки «СтрШапки» и «Заголов»

Также в начало модуля необходимо вынести определения переменных, используемых в строках. У меня это:

Перем СуммаВСтроке, КоличествоВСтроке;


8. Выводим таблицу на экран

По идее, шаги 6 и 8 можно было бы оформлять в отдельную процедуру, потому что они вполне типизированы и могут использоваться практически без изменения во многих отчетах. Но мы для простоты иллюстрации не будем этого делать.

На заключительном шаге построения отчета с группировками реализуем обход по базовой таблице, выводя строки и группировки печатной формы.

Рассмотрим работу построителя отчета по индукции.

На самом первом шаге обхода таблицы, по ее первой строке выводим сперва данные по всем результирующим группировкам, начиная с самой верхней и до предпоследней. Текущие значения, соответствующие группировкам, сохраняем в архиве. Выводим значения по первой строке.

На каждой итерации цикла перед выводом значений по строке будем сравнивать значения реквизитов в строке таблицы с текущими значениями по каждой из группировок, с самой верхней до предпоследней. Если по какой-то из группировок под номером n значение изменилось, то выводим все секции таблицы, относящиеся к группировкам, начиная с этой, и до предпоследней. Новые значения по группировкам опять сохраняем.

В общем-то ничего сложного…

БылГр=СоздатьОбъект("СписокЗначений");

Таб=СоздатьОбъект("Таблица"); 
Таб.ИсходнаяТаблица("Таблица");
Таб.ВывестиСекцию("Шапка");
МаксКолвоСекций=6; 
                                   
Гр="";
ТекГр=1;
Состояние("Постоентие таблицы ...");

Т.ВыбратьСтроки();
Пока Т.ПолучитьСтроку()=1 Цикл
  Для К=1 По ТекГр-1 Цикл
    Если ПроверитьГруппировку(Т,СписокГруппировок,БылГр,К)=0 Тогда
      ТекГр=К;
      Прервать;
    КонецЕсли;	
  КонецЦикла;
  Пока ТекГр<ГрВсего Цикл 
    СуммаГруппировки(Т,Арх[ТекГр],СписокГруппировок,ТекГр,ГрВсего);
    Гр=СписокГруппировок.ПолучитьЗначение(ТекГр,Стр);
    Инф=Т.ПолучитьЗначение(Т.НомерСтроки,Гр);
    Таб.ВывестиСекцию("Строка"+?(МаксКолвоСекций-ГрВсего>0,
      Строка(ТекГр+ МаксКолвоСекций -ГрВсего),ТекГр));
    ЗапомнитьГруппировку(Т,СписокГруппировок,БылГр,ТекГр);
    ТекГр=ТекГр+1;
  КонецЦикла;
  Гр=СписокГруппировок.ПолучитьЗначение(ТекГр,Стр);
  Инф=Строка(Т.ПолучитьЗначение(Т.НомерСтроки,Гр));
  СуммаГруппировки(Т,Т,СписокГруппировок,ТекГр,ГрВсего);
  Таб.ВывестиСекцию("Строка");
КонецЦикла;


На не хватает только проверяющей, сохраняющей значения и рассчитывающей сумму по группировкам процедур.

Функция ПроверитьГруппировку(Т,СпГрупп,БылГр,ТекГр)
  Если ТекГр=0 Тогда
    Возврат 1;
  КонецЕсли;
  Если ТекГр>БылГр.РазмерСписка() Тогда
    Возврат 1;
  КонецЕсли;    
  Гр=СпГрупп.ПолучитьЗначение(ТекГр);
  Зн1=Т.ПолучитьЗначение(Т.НомерСтроки,Гр);
  Зн2=БылГр.ПолучитьЗначение(ТекГр);
  Если Зн1<>Зн2 Тогда
    БылГр.УстановитьЗначение(ТекГр,"явлопваптявлптбяват");
  КонецЕсли;
  Возврат ?(Зн1=Зн2,1,0);
КонецФункции // ПроверитьГруппировку(Т,БылГр,ТекГр)

Процедура ЗапомнитьГруппировку(Т,СпГрупп,БылГр,ТекГр)
  Гр=СпГрупп.ПолучитьЗначение(ТекГр);
  Зн=Т.ПолучитьЗначение(Т.НомерСтроки,Гр);
  Если ТекГр>БылГр.РазмерСписка() Тогда
    БылГр.ДобавитьЗначение(Зн);
  Иначе	
    БылГр.УстановитьЗначение(ТекГр,Зн);
  КонецЕсли;    
КонецПроцедуры //ЗапомнитьГруппировку(Т,БылГр,ТекГр)

Процедура СуммаГруппировки(Т,ТабГр,СпГрупп,ТекГр,КолГр)
  Если ТекГр=КолГр Тогда
    СуммаВСтроке=Т.ПолучитьЗначение(Т.НомерСтроки,"Сумма");
    КоличествоВСтроке=Т.ПолучитьЗначение(Т.НомерСтроки,"Количество");
  Иначе
    Гр=СпГрупп.ПолучитьЗначение(1);               
    Зн=Т.ПолучитьЗначение(Т.НомерСтроки,Гр);
    Поз=0;
    ТабГр.НайтиЗначение(Зн,Поз,Гр);
    Пока Поз<=ТабГр.КоличествоСтрок() Цикл
      Найд=0;
      Для К=2 По ТекГр Цикл
        Гр=СпГрупп.ПолучитьЗначение(К);
        Если ТабГр.ПолучитьЗначение(Поз,Гр)<>Т.ПолучитьЗначение(Т.НомерСтроки,Гр) Тогда
          Поз=Поз+1;
          Найд=1;
          Прервать;
        КонецЕсли;
      КонецЦикла;
      Если Найд=0 Тогда
        СуммаВСтроке=ТабГр.ПолучитьЗначение(Поз,"Сумма");
        КоличествоВСтроке=ТабГр.ПолучитьЗначение(Поз,"Количество");
        Прервать;
      КонецЕсли;
    КонецЦикла;
  КонецЕсли;
КонецПроцедуры 


Отчет готов!

С учетом того, что кроме отчета я успел позаниматься разминкой, поужинать и написать эту статью мне потребовалось около трех часов…

Отчет, который я создавал сейчас вместе с вами, и который может быть легко использован, как шаблон, можно скачать вот здесь: //infostart.ru/projects/1460/

Надеюсь, предложенная технология поможет моим коллегам систематизировать и сократить время на разработку отчетов...

См. также

Комментарии
1. GENDALF (Gendalf) 27.11.07 05:24 Сейчас в теме
5. Формируем базовую таблицу

Вставляем вполне стандартный кусок, не забыв прежде определить переменную Т:

З=СоздатьОбъект("Запрос");
Если З.Выполнить(ТекстЗапроса)=0 Тогда
Сообщить("Какая-то ошибка в запросе!");


Еще лучше... Сообщить ("Что-то не то!") Очень приятно когда тебе утром звонят и говорят приходи у нас "Что-то не то!"
стаья хорошая +
2. Олег Пономаренко (O-Planet) 6669 27.11.07 05:27 Сейчас в теме
>> стаья хорошая +

Ставь!

(и еще один - за скромность этого моего комментария :) )
3. Сhe Burashka (CheBurator) 27.11.07 09:20 Сейчас в теме
Как всегда - я и здесь молчать не буду. Давайте доведем до логического конца описанный выше подход, а именно: разделим функционально "объекты":
- "объект" подготовки данных (сбор через запрос, выборкой и т.д.);
- "объект" вывода произвольных данных в виде таблицы с группировками (вертикальными/горизонтальными).
Свяжем эти 2 объекта соглашением о связях: на вход второго "объекта" (вывод данных) требуется передать ТЗ определенного вида, а именно: плоскую ТЗ произвольного вида и указания "группировочных" измерений и "ресурсов" (если зайти еще дальше, то группировочные измерения и ресурсы можно определять автоматически).
В результате получим "Универсальный отчет по ТЗ": http://infostart.ru/profile/174/projects/942/ и пример его использования: http://infostart.ru/projects/522/?&desc=1&ref=174.
Т.е. описанное в статье вполне можно было бы окончить на п.5 после оператора
З.Выгрузить(Т,1,0);

Каковы преимущества/недостатки "Универсального отчета по ТЗ" и решения Опланета? Они очевидны:
- решение автора более глубоко "привязано" к текущему алгоритму и за счет этого не так легко переносимо;
- "универсальный отчет по ТЗ" абсолютно автономен и встративается в качестве подсистемы вывода в любой отчет, поддерживающий "соглашение" о связях (если бы автор взял его, то написание отчета заняло бы гораздо меньше времени ;)
..но все имеет свою оборотную сторону: решение автора будет быстрее строить итоговый отчет, "универсальный отчет" в этом смысле - помедленнее, потому как самостоятельно фильтрует/строит таблицы-выборки, описывающие группировки.
.. но в итоге (судя по всему) суммарное время, затраченное на выбор данных и вывод итоговой таблицы будет примерно одинаковым... а универсальность и удобство - судить "пользователям".
4. Евгений Мартыненков (JohnyDeath) 290 27.11.07 10:40 Сейчас в теме
В очередной раз советую посмотреть в сторону 1с++ http://www.1cpp.ru/ и уже реализованных классов в том числе по группировкам, МФ и выводу в таблицу. Отличный набор классов вам дарит Лаборатория 33 http://33lab.ru/ и их конфа СКАТ: http://infostart.ru/projects/1149/
Другие классы можно найти или попросить на форуме 1с++ http://www.1cpp.ru/forum/YaBB.pl
Время создания подобных отчетов сокращается в разы!
5. Сhe Burashka (CheBurator) 27.11.07 10:49 Сейчас в теме
До тех пор, пока не будет внятного репозитария классов - да, ускоряется в разы, да хорошо... но! разгребать тонны чтобы понять/найти нужное - тяжко.. согласен - неповоротливый я... Документации ВНЯТНОЙ как не было - так и нет.
6. Евгений Мартыненков (JohnyDeath) 290 27.11.07 11:15 Сейчас в теме
Документации к чему? Если к 1с++, то вот тут: http://www.1cpp.ru/index.php/Documentation есть раздел "Online html документация", который изменяется при внесении каких-то доработок в 1cpp.dll. Если ты имел ввиду документацию к классам, то тут уж извини: если у автора класса есть время на подробное описание, то он это делает, если есть чуть-чуть времени, то он публикует основные вохможности класса и тестовый пример, ну а если вообще нет времени, то... его право, можно сказать спасибо хотя бы за то, что он это сделал и выложил.
П.С. ес-но, скриншотами мало кто балуется ;)
7. Сhe Burashka (CheBurator) 27.11.07 11:25 Сейчас в теме
(6) Про то и речь... все доки, которые я смотрел, представляют собой просто набор чего-то. Как чего-то близкого к понятию "документация" - нет... Про репозитарий - вообще молчу... (а то, что он "сделал и выложил" - не умаляя мастерства работающих на 1С++ - хз..чего он там наваял.. особенно если поленился доку приписать к классу).
8. Олег Пономаренко (O-Planet) 6669 27.11.07 11:45 Сейчас в теме
Рябят, вы не о том. В конце концов, можно установить 1С++, купить КЗК, заказать новейший отчет и вообще перестать о чем-либо думать. Я писал для программистов, а не для копи-пастеров и драг-енд-дроперов. Цель статьи - ПРЕДЛОЖИТЬ МЕТОД и расмотреть его изнутри. А далее - все что угодно. Хотите - делайте свой "универсальный отчет", хотите - используйте чей-то, я же беру вот этот самый шаблон и быстренько переделываю под то, что мне надо. "неповоротливый я" :)
9. Аркадий Кучер (Abadonna) 3667 27.11.07 11:56 Сейчас в теме
(8) Да никто и не спорит! Я, во всяком случае... "Мастер-класс" убери и нет вопросов ;)
10. Олег Пономаренко (O-Planet) 6669 27.11.07 11:59 Сейчас в теме
Ты вот мыло лучше посмотри :)
11. Brr (brr) 27.11.07 11:59 Сейчас в теме
12. Олег Пономаренко (O-Planet) 6669 27.11.07 12:00 Сейчас в теме
[11] У Раруса фсе продукты интересные...
13. Олег Пономаренко (O-Planet) 6669 27.11.07 12:06 Сейчас в теме
[10] Ха, это мне надо было мыло посмотреть ...
14. Brr (brr) 27.11.07 12:06 Сейчас в теме
(12) Мне понравился механизм группировок с автоотступом
15. Евгений Мартыненков (JohnyDeath) 290 27.11.07 12:12 Сейчас в теме
(8) >Я писал для программистов, а не для копи-пастеров и драг-енд-дроперов
улыбнуло ))
Т.е. ты считаешь, что если функционал разбит на классы, то человек, который это использует "драг-н-дропер" и копи-пастер??? А тот, кто прочитает твою статью, скопирует отсюда строки и вставит в своё - это настоящий программист???! :))))
16. Олег Пономаренко (O-Planet) 6669 27.11.07 12:33 Сейчас в теме
Да разбить на классы - не вопрос. Я по ходу об этом и пишу, кстати там же. Просто Ч. предлагает заменить целый кусок использованием независимой обработки. Это можно, без вопросов. Но тогда какой смысл писать статью? Надо просто публиковать ссылки, как он и сделал: возмем кусок оттуда, потом оттуда...
17. S_S (sergiowood) 2 27.11.07 13:40 Сейчас в теме
Такого рода статьи и примеры очень помогают при освоении механизмов построения отчетов в 1С, особенно начинающим или просто любителям бухам. которые пишут для оптимизации своего труда.
А ссылки «У Раруса есть интересный продукт http://www.rarus.ru/products/soft/126/», типа заплати и плыви, не каждому начинающему по карману.
СПАСИБО за статью и пример.
18. Сhe Burashka (CheBurator) 27.11.07 14:14 Сейчас в теме
(16) Не просто ссылки, а хотя бы мелкое, но содержательное описание как их "увязать" между собой... ;-)
19. Олег Пономаренко (O-Planet) 6669 03.12.07 15:24 Сейчас в теме
О! А я уж думал, что оно не нужно оказалось... :)
20. Андрей (SAN) 03.12.07 16:40 Сейчас в теме
Подобная методика используется в конфе "Заказчик строительства". Можешь посмотреть и сравнить. За статью - спасибо. Есть же энтузиасты..
Оставьте свое сообщение