Продолжение статьи “Производство: учёт и контроль”

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

Движения

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

  1. Закупка материала у поставщика
  2. Списание материала на производство
  3. Оприходование готовой продукции
  4. Отгрузка готовой продукции покупателю

В некоторых случаях используются дополнительные движения:

  • Возврат материала поставщику
  • Списание материала в брак
  • Возврат готовой продукции от покупателя
  • Списание готовой продукции в брак
  • Перемещение материала или готовой продукции с одного места хранения в другое

Каждое движение – это отдельная запись в таблице oper, в которой указывается партия номенклатуры (id_pitem), количество (qty) и сумма (amount). Информация о направлении движения находится в заголовке документа, с которым связана операция (id_operDoc).

Заголовок (таблица operDoc) содержит сведения о дате проведения операции (docDate), номер документа (docNum), а также сведения о направлении движения: откуда (id_contr) и куда (id_contr1) перемещаются материальные ценности.

Пример перемещения материальных ценностей

Информация о сумме при перемещении имеет значение только в трёх случаях:

  • Закупка материала у поставщика
  • Оприходование готовой продукции
  • Отгрузка готовой продукции покупателю

Закупка и оприходование определяет закупочную цену партии товара, а отгрузка – цену продажи, что позволяет определить прибыль от продажи. Все остальные перемещения производятся по цене закупки (оприходования), в том числе списание. Таким образом, убытки от брака всегда учитываются по стоимости закупки.

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

Корректировка структуры

Поразмыслив как следует, я решил добавить ещё одну таблицу: unit (единицы измерения). На расчеты она никак не влияет, но добавляет возможность указывать единицы измерения: метры, килограммы, литры и т.д. Это может пригодиться в дальнейшем при формировании печатных отчетов.

Формы

Для обеспечения любого движения нужны соответствующие формы – в этом со мной согласится любой хореограф. Значит нам понадобятся формы справочников и форма журнала документов – табличные формы представления данных. Также нужны формы редактирования – по одной для каждой табличной формы.

Обычно минимальный набор форм можно рассчитать по формуле:

<Количество форм> = <Количество редактируемых таблиц> * 2

Это нужно учитывать при оценке затрачиваемого на разработку времени. Более точная оценка базируется на том факте, что время создания формы пропорционально числу полей таблицы, для которой создаётся форма. При этом нужно учитывать, что в процессе разработки сложность работы (затраченное время) может возрасти за счёт добавления вычисляемых полей или необходимости написания сложных запросов для выборки данных.

Вычисляемые поля ускоряют разработку приложения, так как при написании SQL-запроса нужно заботиться об извлечении данных только для одного поля. Однако, когда My Visual Database начинает использовать вычисляемые поля для формирования своих внутренних запросов к данным для отображения таблиц или форм редактирования, производительность системы может просесть. Это нестрашно на этапе прототипирования, когда нужно быстро создать рабочее приложение. Но в дальнейшем, когда объём хранимых в базе данных вырастет, необходимо провести ревизию и заменить использование вычисляемых полей на полноценные SQL-запросы для выборки табличных данных.

Главная форма и меню

В данном проекте использован принцип MDI (Multi Document Interface – многодокументный интерфейс) – все формы табличного представления отображаются внутри главной формы. А для отображения нужной формы применяются пункты главного меню.

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

Для работы с главным меню добавим функцию добавления пункта меню Menu_Add() и процедуру обработчика выбора пункта меню Menu_ItemOnClick().

function Menu_Add( AName:string; ACaption: string;  AParentItem:TMenuItem;  AIndex:integer = -1; AOnClick:string = 'Menu_ItemOnClick'; ):TMenuItem;
// добавление пункта меню
// AName - имя пункта; закладывается действие и параметр: <действие>_<параметр> ; действия: Show - отобразить форму на рабочей панели главной формы, параметр - имя формы
// ACaption - отображаемое название пункта меню
// AParentItem - родительский элемент; если nil, то пункт меню добавляется на верхний уровень
// AOnClick - обработчик
var
  tmpForm:TForm;
begin
  tmpForm := MainForm; // работаем с меню на главной форме
  Result := TMenuItem.Create( tmpForm );
  // если имя указано, то оформляем его по стандарту имен
  if AName <> '' then
    Result.Name := T_MENU_ITEM + AName; // добавляем префикс класса
  Result.Caption := ACaption;
  // если обработчик не отключен, то добавляем его
  if AOnClick <> '-' then
    Result.OnClick := AOnClick;
  // если родительский элемент не указан, то
  if AParentItem = nil then
  begin // добавляем пункт меню на верхний уровень
    if AIndex = -1 then
      tmpForm.Menu.Items.Add(Result)
    else // или вставляем в указанную в параметре позицию
      tmpForm.Menu.Items.Insert(AIndex,Result);
  end
  else // если указан, то
  begin // добавляем как дочерний
    if AIndex = -1 then
      AParentItem.Add(Result)
    else // или вставляем в указанную в параметре позицию
      AParentItem.Insert(AIndex,Result);
  end;
end;

procedure Menu_ItemOnClick(Sender:TObject);
// клик по пункту меню
var
  tmpItem:TMenuItem;
  tmpAction:string;
  tmpParam:string;
  tmpForm:TForm;
  tmpShowForm:TForm;
  tmpPanel:TdbPanel;
begin
  CForm(Sender,tmpForm);
  tmpItem := TMenuItem(Sender);
  // выделяем из названия действие
  tmpAction := DeleteSuffix(DeleteClassName(tmpItem.Name));
  // выделяем из названия параметр
  tmpParam := Trim(GetSuffix(tmpItem.Name));
  // эта часть может в дальнейшем расширяться
  case tmpAction of
  'Show': begin // показать форму на главной форме
    FindC(tmpForm,'panWork',tmpPanel);
    tmpShowForm := App_GetFormByName(tmpParam); // находим отображаемую форму
    if tmpShowForm = nil then
      RaiseException('Menu_ItemOnClick() не найдена форма: '+tmpParam);
    Form_ShowOnWinControl(tmpShowForm,tmpPanel); // отображаем форму на панели
    // включить в заголовок главной формы название отображаемой формы
    tmpForm.Caption := APP_NAME + ' ' + APP_VERSION + ' - ' + tmpShowForm.Caption;
  end;
  else RaiseException('Menu_ItemOnClick() неизвестная команда: '+tmpAction)
  end;
end;Code language: Delphi (delphi)

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

Описание функций CForm(), DeleteSuffix(), GetSuffix(), App_GetFormByName(), Form_ShowOnWinControl() и констант APP_NAME, APP_VERSION можно найти в проекте "ClassExplorer".

Формы табличного представления

Табличные формы бывают трёх видов:

  • Простые
  • С детализацией
  • Подчиненные

На каждой форме находится табличное представление, панель с кнопками, настроенными для редактирования данных (btnNew, btnEdit, btnDelete). Изображения для кнопок будут загружаться

Рассмотрим каждый вид подробней.

Простые формы

Простые формы предназначены для ведения справочников, состоят из табличного представления и панели инструментов, на которой расположены три кнопки для добавления (Action = [НОВАЯ ЗАПИСЬ]), редактирования (Action=[ПОКАЗАТЬ ЗАПИСЬ]) и удаления (Action=[УДАЛЕНИЕ ЗАПИСИ]).

Подробней о настройке кнопок и табличного представления можно прочитать в моей книге "Создание приложений в My Visual Database. Начальный уровень".

К простым формам в нашем приложении относятся:

  • frmContr – справочник контрагентов
  • frmItemType – справочник типов номенклатуры
  • frmUnit – справочник единиц измерений

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

Формы с детализацией

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

У кнопки btnUpdateDetal установлено свойство ACTION = [SQL запрос], но никакого запроса в настойках кнопки нет, только обработчик события, в котором происходит обновление данных в зависимых таблицах. Это сделано для того, чтобы данную кнопку можно было указать в свойстве Increm, Search таблицы tgrMain. Такая настройка позволяет обрабатывать изменение выбранной записи в таблице вне зависимости от того, чем вызвано изменение: кликом мыши или нажатием клавиш на клавиатуре.

Вот пример такой обработки для документа и его детализации:

procedure frmOperDoc_btnUpdateDetail_OnClick (Sender: TObject; var Cancel: boolean);
begin
  // передать параметр на зависимую форму
  frmOper.edtIDMaster.Value := frmOperDoc.tgrMain.dbItemID;
  // обновить данные
  frmOper.btnUpdate.Click;
  // отменить действие кнопки
  Cancel := True;
end;
Code language: Delphi (delphi)

Для размещения формы детализации на соответствующей вкладке многостраничника используется процедура Form_ShowOnWinControl(), которая вызывается в пользовательской процедуре инициализации форм приложения UserApp_InitForm(). Пример вызова для детализации документа:

  Form_ShowOnWinControl( frmOper, frmOperDoc.tshOper );Code language: CSS (css)

Формы с детализацией в данном приложении:

  • frmItem – справочник номенклатуры
  • frmOperDoc – журнал документов

Подчиненные формы

Подчиненные формы в своей основе также имеют табличное представление и кнопки редактирования, но содержимое таблицы строится с помощью кнопки btnUpdate с действием [ПОИСК]. В качестве элемента для фильтрации используется элемент редактирования текста edtIDMaster.

Хотя обычно для фильтрации данных используют выпадающий список (TdbComboBox), в данном случае использование поля для ввода текста (TdbEdit) предпочтительней. Это связано с тем, что каждый выпадающий список автоматически обновляется как при запуске программы, так и при редактировании таблицы, к которой он относится, что в свою очередь приводит к замедлению работы приложения и ненужному расходу ресурсов компьютера.

Подчиненные формы приложения:

  • frmOper – операции движения (детализация документа)
  • frmPItem – партии номенклатуры
  • frmProductItem – состав изделия

Формы редактирования

Для каждой табличной формы имеется соответствующая форма редактирования:

  • efmCont
  • efmItem
  • efmItemType
  • efmOper
  • efmOperDoc
  • efmPItem
  • efmProductItem
  • efmUnit

Для всех форм выбран общий дизайн: в центральной части формы расположены элементы редактирования, а внизу – панель управления, на которой находятся кнопки сохранения btnSave и отмены btnCancel.

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

Эта связь устанавливается при создании новой записи с помощью скрипта, который вызывается при открытии формы редактирования. Например, как при открытии формы редактирования состава изделия:

procedure efmProductItem_OnShow (Sender: TObject; Action: string);
begin
  if Action = 'NewRecord' then
  begin
    efmProductItem.edtIDMaster.Value := frmItem.tgrMain.dbItemID;
  end;
end;
Code language: Delphi (delphi)

Так как при оформлении прихода может возникнуть необходимость создания новой партии (а такая необходимость возникает каждый раз, когда меняется цена закупки), то на форме efmOper имеется специальная кнопка добавления новой партии btnNew_PItem.

Не стоит настраивать эту кнопку непосредственно на добавление данных ( Action = [НОВАЯ ЗАПИСЬ] ). Дело в том, что согласно внутренней логике работы MVDB при нажатии такая кнопка попытается сохранить текущую запись. Такая логика хорошо работает для редактирования таблиц детализации, но не подходит в нашем случае. Поэтому здесь использована обычная кнопка и небольшой скрипт:

procedure efmOper_btnNew_PItem_OnClick (Sender: TObject; var Cancel: boolean);
begin
  // это нужно, чтобы после сохранения не сбрасывался ID записи
  efmPItem.btnSave.dbDontResetID := True;
  // открыть форму редактирования в режиме добавления данных
  efmPItem.NewRecord('pitem');
  // выбрать созданный элемент
  efmOper.cmbPItem.dbItemID := efmPItem.btnSave.dbGeneralTableId;
end;
Code language: Delphi (delphi)

Продолжение следует

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *