Участник:ArmorAdmin/Нумератор

Материал из Бронетанковой Энциклопедии — armor.kiev.ua/wiki
< Участник:ArmorAdmin
Версия от 12:25, 2 февраля 2010; ArmorAdmin (обсуждение | вклад) (Решение (проектирование))

Перейти к: навигация, поиск

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

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

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

Как-то раз вместо техзадания на такой «нумератор», на которое понадобилось бы порядка недели, я за пару дней набросал в Delphi 7 работающий прототип такого нумератора.

Недавно поднял исходники прототипа нумератора и за несколько вечеров в Delphi 2009 довёл их до готовности к использованию в любом проекте, в котором необходимо присваивать каким-либо сущностям номера отличные от обычных чисел. Как оказалось, сделать «универсальный» нумератор не так уж и сложно.

Анализ проблемы

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

Например, номер 03-123/2010 может означать 123-й по счету документ номенклатуры с кодом 03 в 2010 году. Этот же номер с сокращенной записью года может иметь вид: 03-123/10.

Встречаются случаи, когда счетчик работает в рамках других периодов, например в течении дня. Такой номер 3-2010/01/31 может означать третий документ за 31 января 2010 года.

Отсюда можно сделать выводы:

  • заранее формат номера документа знать невозможно, должна быть возможность его настроить;
  • в номере могут использоваться различные составные части:
    • текстовые значения (константы, задаваемые при настройке);
    • даты и их составные части;
    • различные счетчики (триггеры), которых для одной системы может быть несколько;
    • значения атрибутов текущего документа.

Решение (проектирование)

Принятое решение предполагает возможность повторного использования программного кода нумератора в различных проектах.

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

Нумератор отвечает за:

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

Внешняя система отвечает за:

  • хранение настроенных форматов номеров в их привязке к тем сущностям (видам документов), для которых он настраивался;
  • передачу нумератору списков используемых в системе счетчиков и используемых для текущего вида документа полей;
  • передачу нумератору значения поля или счетчика, который у системы запрошен нумератором.

Поскольку нумератор (класс TCounter, см. ниже диаграмму) заранее не знает о внешней системе, к которой он будет подключен, то вместе с ним в одном модуле определён интерфейс ICounter, определяющий методы взаимодействия с внешней системой. Кроме того, в этом же модуле определен абстрактный класс TCounterPart, инкапсулирующий от TCounter поведение различных типов составных частей номера, и наследники TCounterPart, реализующие поведение конкретных типов частей номера.

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

Подробнее см. на диаграмме классов нумератора.

Диаграмма классов нумератора

Реализация

unit counter;

interface

uses
  Classes, StdCtrls, ExtCtrls, Controls, SysUtils, RegExpr, Dialogs;

type

ICounters = interface(IUnknown)
  ['{75533674-8A39-4501-97E7-0A8F9BB5625F}']
  // Возвращает список имеющихся во внешней системе счётчиков
  procedure GetTriggers(aList: TStrings);

  // Возвращает следующее значение счётчика
  function TriggerNext(TriggerName: string): string;

  // Возвращает список системных полей SystemCode=Caption
  // SystemCode - код поля в системе
  // Caption - название поля
  procedure GetSystemCodes(aList: TStrings);

  // Возвращает текущее значение поля в системе
  function SystemCode(CodeName: string): string;
end;


TCounterPart = class;
TCounterPartClass = class of TCounterPart;

TCounter = class(TStringList)
private
  FCounterName: string;
  FICounters: ICounters;
public
  constructor Create(aName: string; I: ICounters; template: string = '');
  procedure AddPart(Part: TCounterPart);
  procedure Clear; override;
  function GetTemplate: string;
  procedure ParceTemplate(template: string);
  function GetSample: string;
  function Generate: string;
  property CounterName: string read FCounterName write FCounterName;
  property CounterInterface: ICounters read FICounters;
end;

TCounterPart = class abstract (TPersistent)
private
  FValue: string;
  FCounter: TCounter;
  function GetValue: string;
  procedure SetValue(const aValue: string);
public
  function GetControlType: TControlClass; virtual;
  function GetName: string; virtual;
  function GetTemplate: string;
  function GetSample: string; virtual;
  function Generate: string; virtual;
  procedure InitControlData(aControl: TControl); virtual;
  property Value: string read GetValue write SetValue;
end;


TCounterPartText = class(TCounterPart)
public
  function GetName: string; override;
  function GetControlType: TControlClass; override;
end;

TCounterPartDate = class(TCounterPart)
public
  function GetName: string; override;
  function GetControlType: TControlClass; override;
  function Generate: string; override;
  procedure InitControlData(aControl: TControl); override;
end;

TCounterPartTrigger = class(TCounterPart)
public
  function GetName: string; override;
  function GetControlType: TControlClass; override;
  function Generate: string; override;
  procedure InitControlData(aControl: TControl); override;
  function GetSample: string; override;
end;

TFieldsComboBox = class(TComboBox)
private
  FFieldItems: TStrings;
  procedure SetFieldItems(const Value: TStrings);
  function GetFieldName: string;
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
  property FieldItems: TStrings read FFieldItems write SetFieldItems;
  property FieldName: string read GetFieldName;
end;

TCounterPartField = class(TCounterPart)
public
  function GetName: string; override;
  function GetControlType: TControlClass; override;
  function Generate: string; override;
  procedure InitControlData(aControl: TControl); override;
end;


const

  PartsClassArray : array [0..3] of TCounterPartClass = (
    TCounterPartText,
    TCounterPartDate,
    TCounterPartTrigger,
    TCounterPartField
  );

implementation

{.......}



var
  PC: TCounterPartClass;

initialization

for PC in PartsClassArray do
  RegisterClass(PC);

end.

Пример использования