Участник:ArmorAdmin/Нумератор — различия между версиями
м (→Решение (проектирование)) |
м (→Анализ проблемы) |
||
Строка 22: | Строка 22: | ||
** текстовые значения (константы, задаваемые при настройке); | ** текстовые значения (константы, задаваемые при настройке); | ||
** даты и их составные части; | ** даты и их составные части; | ||
− | ** различные счетчики (триггеры), которых | + | ** различные счетчики (триггеры), которых в основной программе может быть несколько; |
** значения атрибутов текущего документа. | ** значения атрибутов текущего документа. | ||
Версия 09:37, 2 февраля 2010
Во многих системах документоборота, реестрах и т. п. используются различные схемы нумерации документов. Часто схема нумерации зависит от субъективного понимания удобства присвоения и использования номера конкретными исполнителями организации, которые организовывают её документооборот.
При реализации программного обеспечения, отвечающего за автоматизацию документооборота или его отдельных частей, разработчики часто сталкиваются с этой проблемой. Как правило, к сожалению, разработчики идут «простым» путём и жёстко реализовывают в программе схему присвоения номера документа. В последующем, при изменении внутренних инструкций документооборота предприятия и/или порядка нумерации документов, программа перестаёт отвечать реалиям жизни и требует переработки.
Не раз наблюдал попытки сделать «универсальный нумератор», который у разработчиков получался сложным с точки зрения реализации и часто очень трудным в использовании и применении.
Как-то раз вместо техзадания на такой «нумератор», на которое понадобилось бы порядка недели, я за пару дней набросал в 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.