На главную   На главную   Форумы Новости Документация Скачать Купить  
Регистрация  
Система Allegro
Oб Allegro Характеристики Пример конфигурации Документация База ошибок Развитие
Версия для печати К списку книг Вернуться к оглавлению Предыдущий параграф Следующий параграф
Поиск по книге

Глава 9. СОЗДАЕМ ОКОННЫЙ ИНТЕРФЕЙС ДОКУМЕНТА «ПРОДАЖА»

Создаем оконный интерфейс документа «Продажа» путем копирования

Мы создадим проект оконного интерфейса, взяв за основу имеющийся у нас проект «Поступления на склад».

Включим режим «Дизайнер».

Откроем проект stock_in_project.ipr и сохраним проект под новым именем, используя пункт меню Файл /Сохранить Проект как…



Назовем файл проекта sale_project.ipr



Теперь сохраним входящие в проект модули stock_in. pas и stock_in_header.pas под новыми именами: sale.pas и sale_header .pas. Для этого выберем по очереди соответствующие закладки в редакторе текста и вызовем пункт меню Файл/Сохранить как…




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

Итак, теперь у нас имеется новый проект, полученный путем «клонирования» старого. Но нам необходимо внести еще ряд изменений, прежде чем пробовать его запустить. Прежде всего следует заменить ссылки в секции uses раздела implementation . В модуле sale в этой секции нужно заменить

uses stock_in_header;

на

uses sale_header;

Аналогично в модуле sale_header в этой секции нужно заменить

uses stock_in;

на

uses sale;

Везде в модуле заменим упроминание главной таблицы STOCK_IN на SALE.

Переименуем в Инспекторе объектов форму StockInForm в SaleForm и изменим ее заголовок Caption на «Продажа», а форму StockInHeaderForm в SaleHeaderForm. При помощи поиска Ctrl+R заменим все упоминания формы StockInHeaderForm в тексте модуля sale на SaleHeaderForm (таких упоминаний должно быть 2). Аналогично заменим все упоминания формы StockInForm во втором модуле заменим на SaleForm.

На форме SaleForm удалим компонент DBText6 и Label6 с надписью «Кредитуемый счет». В компоненте Label5 в свойстве Caption заменим значение «Поставщик» на «Покупатель», а в компоненте Label2 значение «Поступило» на «Отгружено ».

Сохраним все изменения. Теперь нам необходимо заменить тексты SQL -запросов, чтобы компоненты qryMaster и qryDetail обращались не к таблицам документа «Поступление на склад», а к таблицам документа «Продажа». Для этого в свойстве SelectSQL компонента qryMaster вместо старого текста введем новый:

select
  S.ID,
  S.DIR_ID,
  S.DOC_DATE,
  S.DOC_NO,
  S.ENTRY_DATE,
  S.HAS_ENTRY,
  S.STOCK_ACC,
  A1.NAME STOCK_ACC_NAME,
  S.LAYER_ID,
  L.SHORT_NAME LAYER_NAME,
  S.VAT_RATE,
  S.CONTRAGENT,
  O.SHORT_NAME CONTRAGENT_NAME,
  S.CALC_MODE,
  S.EXCH_RATE_L,
  S.EXCH_RATE_S,
  S.TOTAL_L,
  S.TOTAL_R,
  S.TOTAL_S
  S.PRICE_TYPE,
  S.PRICE_DISCOUNT
from
  SALE S,
  OBJECT_NAMES O,
  LAYER L,
  ACC A1
where
  S.CONTRAGENT = O.OBJECT_ID and
  S.LAYER_ID = L.LAYER_ID and
  S.STOCK_ACC = A1.ACC_ID and
  S.ID = :ID

Удалим также тексты запросов из свойств InsertSQL, ModifySLQ, RefreshSQL.

Дважды щелкнем на компоненте qryMaster и в редакторе полей удалим поля DOC_KIND, ACC_ID и CREDIT _ACC_NAME.

С помощью контекстного меню компонента qryMaster вызовем DataSet Editor. Выберем все поля таблицы SALE с помощью кнопки Get Table Fields. В списке Key Fields выберем ключевое поле ID , а в списке Update Fields все имеющиеся поля и нажмем кнопку Generate SQL.



Будут автоматически сформированы SQL-запросы InsertSQL, ModifySQL, DeleteSQL и RefreshSQL. Сохраним изменения кнопкой OK.

Удалим текст запроса в свойстве DeleteSQL. А текст из SelectSQL перепишем в RefreshSQL.

Теперь займемся компонентом qryDetail. Заменим в свойстве SelectSQL в тексте запроса лишь имя таблицы STOK_IN_ITEM на SALE_ITEM. В остальном запрос оставим тем же:

select
  SI.ID,
  SI.N,
  SI.GOODS,
  O.SHORT_NAME ITEM_NAME,
  SI.QUANTITY,
  SI.PRICE_L,
  SI.PRICE_L_WO_VAT,
  SI.PRICE_R,
  SI.PRICE_R_WO_VAT,
  SI.AMOUNT_L,
  SI.AMOUNT_R,
  SI.AMOUNT_S
from
  SALE_ITEM SI,
  OBJECT_NAMES O
where
  SI.GOODS = O.OBJECT_ID and
  SI.ID = :ID
order by SI.N

Ту же замену имени таблицы с STOK_IN_ ITEM на SALE_ITEM предпримем в текстах SQL- команд в свойствах InsertSQL, ModifySQL, DeleteSQL, RefreshSQL .

Заменим генератор в свойстве GeneratorField со значения STOCK_IN _ITEM_N_GEN на SALE_ITEM _N_GEN.

В тексте модуля sale удалим команды:

 qryMaster.FieldByName('DOC_KIND').AsInteger := 0;
 qryMaster.FieldByName('ACC_ID').AsInteger := 1012; //Поставщики

Вместо них вставим команды:

qryMaster.FieldByName('PRICE_TYPE').AsInteger := 0;
qryMaster.FieldByName('PRICE_DISCOUNT').AsCurrency := 0;

Дважды щелкнем на компоненте qryMaster и в редакторе полей удалим все прежние поля и добавим все поля заново с помощью пункта контекстного меню «Добавить поля» редактора полей:



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

Теперь перейдем к форме «шапки». Компоненты на этой форме достались нам от формы «шапки» «Поступления на склад». Некоторые компоненты мы оставим, а некоторые – удалим.

Удалим с формы SaleHeaderForm все компоненты, предназначенные для редактирования полей «Вид документа» и «Кредитуемый счет», так как этих полей в документе «Продажа» нет . Изменим надписи в компонентах Label:

«Поставщик» на «Покупатель»

«Дата поступления на склад» на «Дата отгрузки со склада»

Изменим свойство Caption компонента-птички cbHasEntry:

«Товар поступил на склад» на «Товар отгружен »

Добавим компонент Label с палитры Standard, назначив ему Caption «Скидка в цене».

Добавим компонент DBEdit с палитры DataControls и назначим ему свойства :


свойство Значение
Name edPRICE_DISCOUNT
DataSource SaleForm.dsrMaster
DataField PRICE_DISCOUNT

Добавим компонент DBRadioGroup с палитры Data Controls и назначим ему свойства:


свойство Значение
Name RgPRICE_TYPE
DataSource SaleForm.dsrMaster
DataField PRICE_TYPE
Caption Использовать цены
Items Оптовые Розничные
Values 0 1

Расположим все оконные органы управления так, как показано на рисунке:



Установим по вкусу порядок перехода фокуса в этом окне, вызвав редактор свойства «Порядок перехода» (TabOrder) компонентов с помощью контекстного меню компонента формы:


 

Сохраним все изменения. Установим контекст запуска проекта. Для этого с помощью меню Запуск/Переменные контекста вызовем окно «Переменные контекста»:



Выберем переменную RunContext.Documents[0].doc_ type_id (id типа документа). Нажав на кнопку с многоточием , вызовем диалог выбора типа документа, выберем в нем «Продажу» и нажмем кнопку OK :



Выберем вторую переменную - RunContext.Documents[0]. doc_id (id документа). Нажав на кнопку с многоточием , вызовем диалог выбора документа, выберем какой -нибудь документ и нажмем кнопку OK:



Значения переменных контекста установлены. Нажмем кнопку OK, чтобы сохранить их.



Запустим проект (F9).

Проверим, как работает редактирование шапки и позиций, как происходит поиск по нажатию клавиш.

Для того чтобы завершить проект интерфейса «Продажа», нам предстоит еще большая работа. Нам предстоит реализовать:

  1. Копирование цен из справочника товаров в документ
  2. Определение средней себестоимости товаров при сохранении документа, если товар отгружен.
  3. Печать документов типа «Счета-фактуры» и « Накладной»

Начнем с копирования цены при добавлении товаров из справочника. Заказчик желает, чтобы скидка покупателю распространялась непосредственно на цену товара. Следовательно, нам не только придется копировать цену из справочника, но и изменять ее в соответствии со скидкой.

У нас имеется два способа добавить товар в документ « Продажа»:

  1. Найти товар «поиском по нажатию клавиш» и вставить его из нижней сетки клавишей Enter.
  2. Нажать кнопку с многоточием в поле «Наименование» позиции документа и в появившемся окне диалога «Выбрать из справочника » найти нужный нам товар и нажать кнопку «Выбрать »

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

Итак, изменим текст запроса в свойстве SQL компонента qryGoods на:

SELECT O1.OBJECT_ID, O1.SHORT_NAME, G.PRICE_W, G.PRICE_R
FROM GOODS G, OBJECT_NAMES O1
WHERE G.ID = O1.OBJECT_ID

Фактически мы просто добавили два поля в запрос.

Дважды щелкнем на компоненте qryGoods и в редакторе полей с помощью контекстного меню вызовем пункт Добавить все поля…



Нажмем кнопку OK. Выбирая в редакторе полей поля, назначим им свойства в Инспекторе объектов:


Поле DisplayLabel Alignment
PRICE_W Опт.Цена taCenter
PRICE_R Розн.Цена taCenter

Закроем редактор полей. Выберем нижнюю сетку dbgGoods и вызовем редактор колонок с помощью контекстного меню или дважды щелкнув в Инспекторе объектов на свойстве Columns. Добавим все колонки в редактор с помощью соответствующего пункта его контекстного меню и удалим затем колонку OBJECT_ID.

Изменим также текст модуля в части формирования текста запроса « по нажатию клавиш». Это происходит в обработчике события OnSetEditText сетки dbgDetail:

  {Конструирование запроса из строки признаков, разделенных пробелами}
  s := 'SELECT O1.OBJECT_ID, O1.SHORT_NAME, G.PRICE_W, G.PRICE_R '#13+
       'FROM GOODS G, OBJECT_NAMES O1'#13+
       'WHERE G.ID = O1.OBJECT_ID AND O1.OBJECT_ID <> 0';

Запустим проект. Теперь при поиске «по нажатию клавиш » в нижней сетке товары отображаются со своими ценами:



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

Остановим проект. Добавим вызов процедуры, которая выделена жирным шрифтом, в обработчик события OnDblClick нижней сетки:

{Копирование позиции из нижнего списка товаров двойным щелчком мыши}
procedure TSaleForm.dbgGoodsDblClick(Sender: TObject);
begin
  qryDetail.Edit; {Присвоение значений полям позиции}
  qryDetail.FieldByName('GOODS').AsInteger :=
     qryGoods.FieldByName('OBJECT_ID').AsInteger;
  qryDetail.FieldByName('ITEM_NAME').AsString :=
     qryGoods.FieldByName('SHORT_NAME').AsString;
  UpdatePrice(qryGoods);
  LastValue := qryDetail.FieldByName('ITEM_NAME').AsString;
  dbgDetail.SelectedField := qryDetail.FieldByName('QUANTITY');
  dbgDetail.SetFocus; //Возвращение фокуса ввода в сетку позиций
end;

Добавим в секцию private класса формы TSaleForm объявление процедуры ( выделено жирным):

  private
    { Private declarations }
    procedure UpdatePrice(PriceQuery: TDataSet);
  public
    { Public declarations }
  end;

Добавим в раздел implementation модуля sale реализацию этой процедуры:

{Вставка цены}
procedure TSaleForm.UpdatePrice(PriceQuery: TDataSet);
var
  price_s, price_l: Extended;
begin
  {В зависимости от типа используемой цены}
  if qryMaster.FieldByName('PRICE_TYPE').AsInteger = 1 then
    price_s := PriceQuery.FieldByName('PRICE_R').AsCurrency
  else
    price_s := PriceQuery.FieldByName('PRICE_W').AsCurrency;
  {Применение скидки и пересчет в валюту документа}
  price_l := price_s *
    (1 - qryMaster.FieldByName('PRICE_DISCOUNT').AsCurrency/100)*
     qryMaster.FieldByName('EXCH_RATE_S').AsCurrency/
     qryMaster.FieldByName('EXCH_RATE_L').Ascurrency;
  {В зависимости от режима расчета (от цен с НДС или без НДС)
   происходит окгругление}
  if qryMaster.FieldByName('CALC_MODE').AsInteger = 1 then
    qryDetail.FieldByName('PRICE_L').AsCurrency :=
      round(price_l * 100)/100
  else
    qryDetail.FieldByName('PRICE_L_WO_VAT').AsCurrency :=
      round(price_l * 10000/
           (qryMaster.FieldByName('VAT_RATE').AsCurrency + 100))/100;
end;

Согласно договоренности с заказчиком, в справочнике товаров хранятся цены с НДС. Поэтому способ расчета сумм CACL_MODE (от цены с НДС или от цены без НДС ) в документе «Продажа» имеет несколько иное значение , чем в документе «Поступление на склад». Если в документе «Продажа» выбран режим CALC_MODE  = 0 (расчет от цен без НДС), то берется за основу справочная цена с НДС, из нее вычитается НДС, затем цена округляется до двух знаков после запятой и вставляется в документ. После таких преобразований цена с НДС в документе может уже не совпадать со справочной из-за ошибок округления. Если же выбран режим CALC_MODE = 1 (режим по умолчанию), то в документ копируются цены с НДС из справочника, а цены без НДС вычисляются с точностью до 3 знаков после запятой. В этом режиме цены с НДС в документе соответствуют справочным.

Запустим проект и убедимся, что расчет цен работает при разных валютах документа и в разных режимах «использовать цену ».

Нам осталось реализовать вызов процедуры UpdatePrice при втором способе добавления товаров в документ – из окна диалога «Выбрать из справочника», которое вызывается нажатием кнопки с многоточием в сетке позиций. Добавим в событие OnEditButtonClick сетки dbgDetail текст, выделенный жирным шрифтом:

procedure TSaleForm.dbgDetailEditButtonClick(Sender: TObject);
var
  Q: TIBQuery;
begin
  with RefDialog1 do
  if Execute then //вызываем диалог на экран и проверяем, был ли выбран товар
  begin
    qryDetail.Edit; //переводим набор данных в режим редактирования
    {присваиваем значение ID товара полю GOODS набора}
    qryDetail.FieldByName('GOODS').AsInteger := Object_ID;

    {создаем run-time компонент запроса и запрашиваем цену товара}
    Q := TIBQuery.Create(nil);
    try
      Q.Transaction := self.traCurrent;
      Q.SQL.Text := 'SELECT PRICE_W, PRICE_R FROM GOODS WHERE ID = :ID';
      Q.ParamByName('ID').AsInteger := Object_ID;
      Q.Open;
      UpdatePrice(Q);
    finally
      Q.Free;
    end;

    {присваиваем значение краткое наименование товара полю ITEM_NAME набора}
    qryDetail.FieldByName('ITEM_NAME').AsString:=
                       NameOfRefObject(Object_ID, 'SHORT_NAME');
    {перемещаем фокус ввода в сетке в поле "Количество"}
    dbgDetail.SelectedField := qryDetail.FieldByName('QUANTITY');
  end;
end;

Запустим проект (F9) и убедимся, что при выборе товара из справочника в диалоге справочная цена копируется в позицию документа.

Нам еще нужно добиться того, чтобы скидка, предоставляемая покупателю, копировалась из справочника в документ сразу после выбора покупателя из справочника контрагентов. Обычно это происходит на стадии заполнения шапки документа, когда пользователь создает новую «продажу ».

Для этого выберем на форме SaleHeaderForm компонент refCONTRAGENT и создадим обработчик его события OnExecuted. В обработчик впишем такой текст :

procedure TSaleHeaderForm.refCONTRAGENTExecuted(Sender: TObject);
var
  Q: TIBQuery;
begin
  {создаем run-time компонент запроса и запрашиваем скидку}
  Q := TIBQuery.Create(nil);
  try
    Q.Transaction := SaleForm.traCurrent;
    Q.SQL.Text := 'SELECT DISCOUNT FROM FIRM WHERE ID = :ID';
    Q.ParamByName('ID').AsInteger := refCONTRAGENT.Object_ID;
    Q.Open;
    {Копируем скидку в шапку документа}
    SaleForm.qryMaster.FieldByName('PRICE_DISCOUNT').AsCurrency :=
       Q.Fields[0].AsCurrency;
  finally
    Q.Free;
  end;
end;

Выйдем из режима дизайнера и вызовем окно «Метаданные». Выберем тип документа «Продажа» и в «Дополнительных свойствах документа» на закладке «Интерфейс» назначим проект оконного интерфейса sale_project.ipr. Установим птичку «создавать документ из проводника» и выберем запуск « Множество экземпляров». Нажмем кнопку «Сохранить». Вызовем « Проводник по документам» и убедимся, что теперь документы продаж вызываются двойным щелчком на экран. Попытаемся создать новый документ «Продажа» с помощью соответствующего пункта контекстного меню «Проводника по документам»:



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

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



Пример создания склада в Allegro Наверх