Отправляет email-рассылки с помощью сервиса Sendsay

Создаем свой бизнес

  Все выпуски  

Создаем свою информационную систему


Создаем свою информационную систему


Инвентаризация. 1

 

Итак сегодня попробуем обсудить конкретный проект и перепетии его реализации.

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

 

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

 

Ситуация в общем-то банальная, для больших предприятий. На самом деле в рамках «предприятия» существует много юридических лиц, которые территориально находятся в одном месте. Огромное количество объектов, которые можно отнести к материалам и основным средствам. Ну и нам надо, хотя бы раз в год проверить наличие этих средств, т.е. сравнить их с данными учета. Итогом работы является документ(ы) в котрых написано было столько-то, найдено – столько-то.

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

За этот стол отвечает Иванов, за омпьютер – Петров. Эти люди могут двигаться по служебной лестнице переводиться и пересаживаться с места на место, увольняться. А еще они могут что-то сломать, или потерять….

 

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

 

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

 

Это я пока привожу только начальную постановку, и не совсем полную.

 

Ну чтоже главное ввязаться в бой.

 

Через пару месяцев разговоров вокруг да около проект вошел в стадию реализации.

 

Первой «засадой» оказалось требование от IT службы, о том что нельзя ставить никакого дополнительного софта кроме самой программы, в том числе и на сервер, причем там особо было указано, что MS SQL Server и .Net не должны устанавливаться.

 

Карта была разыграна очень просто, на обсуждение ТЗ посылается специалист из IT службы, Который соглашается, что в ТЗ разговор только про рабочие станции, а требование выдвигает начальник службы, который на этом обсуждении не был и ничего кроме ТЗ не признает. Как говорил Эзоп, - Отсюда мораль: Закрепляйте все в ТЗ! Никакие протоколы обсуждения, даже подписанные вам скорее всего не помогут.

 

Итак мы оказались перед необходимостью использовать бесплатную базу данных, которую относительно просто развернуть из дистрибутива.

 

Если честно, то я подозревал что будет именно так. И, слава Богу, наш подход не привязан жестко к типу базы данных.

Да, конечно, нам пришлось срочно собирать генератор для новой базы. А выбрали мы PostgreSQL.

И, конечно, имея опыт создания генераторов это было не так долго. И, самое интересное, что мы получили некий козырь перед заказчиком, потому как вписались в их требования, точно зная, что в ТЗ есть относительно плохо проработанные места и нам будет чем «ответить» на страшном суде.

 

Техническая сторона реализации генератора.

Первое, что надо сделать - это понять на что похож подход при реализации базы данных.

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

 

Затем надо построить правилное отображение используемых в моделировании типов данных на хранилище.

 

Вот пример одно из вариантов такого отображения:

Тип

Коментарий

Тип в базе данных

База данных

Date

Дата

DATETIME

MS SQL 2000

Date

Дата

DATE

ORACLE

Date

Дата

date

PGSQL

DateTime

Дата и время

DATETIME

MS SQL 2000

DateTime

Дата и время

DATE

ORACLE

DateTime

Дата и время

timestamp

PGSQL

E-MAIL

Почтовый адрес

varchar

MS SQL 2000

E-MAIL

Почтовый адрес

VARCHAR2

ORACLE

E-MAIL

Почтовый адрес

varchar

PGSQL

File

Файл

IMAGE

MS SQL 2000

File

Файл

VARCHAR2

ORACLE

File

Файл

bytea

PGSQL

HTML

Документ в формате HTML

TEXT

MS SQL 2000

HTML

Документ в формате HTML

VARCHAR2

ORACLE

HTML

Документ в формате HTML

TEXT

PGSQL

ID

Идентификатор

uniqueidentifier

MS SQL 2000

ID

Идентификатор

CHAR

ORACLE

ID

Идентификатор

uuid

PGSQL

Image

Изображение (картинка)

IMAGE

MS SQL 2000

Image

Изображение (картинка)

VARCHAR2

ORACLE

Image

Изображение (картинка)

BYTEA

PGSQL

Integer

Целое число

integer

MS SQL 2000

Integer

Целое число

NUMBER

ORACLE

Integer

Целое число

integer

PGSQL

MasterString

Мастер строк

VARCHAR

MS SQL 2000

MasterString

Мастер строк

VARCHAR

ORACLE

MasterString

Мастер строк

VARCHAR

PGSQL

Memo

Многострочное поле для ввода информации

TEXT

MS SQL 2000

Memo

Многострочное поле для ввода информации

VARCHAR2

ORACLE

Memo

Многострочное поле для ввода информации

TEXT

PGSQL

MultiLanguage Memo

NULL

TEXT

MS SQL 2000

MultiLanguage Memo

NULL

VARCHAR2

ORACLE

MultiLanguage Memo

NULL

TEXT

PGSQL

MultiLanguage String

Строка с поддержкой многоязычности

VARCHAR

MS SQL 2000

MultiLanguage String

Строка с поддержкой многоязычности

VARCHAR2

ORACLE

MultiLanguage String

Строка с поддержкой многоязычности

VARCHAR

PGSQL

Numeric

Число

float

MS SQL 2000

Numeric

Число

NUMBER

ORACLE

Numeric

Число

float

PGSQL

Password

Пароль

varchar

MS SQL 2000

Password

Пароль

VARCHAR2

ORACLE

Password

Пароль

varchar

PGSQL

Reference

Ссылка

UNIQUEIDENTIFIER

MS SQL 2000

Reference

Ссылка

CHAR

ORACLE

Reference

Ссылка

uuid

PGSQL

ReferenceSQL

Ссылка на источник данных - SQL запрос

TEXT

MS SQL 2000

ReferenceSQL

Ссылка на источник данных - SQL запрос

VARCHAR2

ORACLE

ReferenceSQL

Ссылка на источник данных - SQL запрос

TEXT

PGSQL

RTF

RTF текст

TEXT

MS SQL 2000

RTF

RTF текст

VARCHAR2

ORACLE

RTF

RTF текст

TEXT

PGSQL

String

Строка ограниченной длины

VARCHAR

MS SQL 2000

String

Строка ограниченной длины

VARCHAR2

ORACLE

String

Строка ограниченной длины

VARCHAR

PGSQL

Time

Время

datetime

MS SQL 2000

Time

Время

DATE

ORACLE

Time

Время

time

PGSQL

URL

Адрес ресурса в интернет

varchar

MS SQL 2000

URL

Адрес ресурса в интернет

VARCHAR2

ORACLE

URL

Адрес ресурса в интернет

varchar

PGSQL

 

Затем надо «построить» генератор базы данных

 

Для примера я приведу три идентичных фрагмента генератора для разных типов баз данных

 

PostgreSQL

Private Sub CreateBriefProc(os As PART)

DebugOutput "POSTGRESGEN.CreateBriefProc:start "

Dim st As PART

Set st = os

Dim chos As PART, i As Long, j As Long, f As FIELD

Dim s As Writer

Set s = New Writer

On Error GoTo bye

s.putBuf ""

s.putBuf " create or replace function " & VF(os.Name) & "_BRIEF ("

s.putBuf " aCURSESSION uuid,"

s.putBuf " a" & VF(os.Name) & "id uuid"

s.putBuf ") returns varchar as $$"

s.putBuf "declare"

s.putBuf " aBRIEF varchar(255);"

s.putBuf " aaccess integer;"

s.putBuf " atmpStr varchar(255);"

s.putBuf " atmpID uuid;"

s.putBuf " existsCnt integer;"

s.putBuf "begin"

s.putBuf " -- checking the_session --"

s.putBuf " select count(*) into existsCnt from the_session where the_sessionid=acursession and closed=0 ;"

s.putBuf "if existsCnt=0"

s.putBuf " then"

s.putBuf " perform raise_application_error(-20000,'Сессия уже завершена.');"

s.putBuf " return '';"

s.putBuf " end if;"

 

s.putBuf "if a" & VF(os.Name) & "id is null then aBRIEF:=''; return aBrief; end if;"

 

s.putBuf " -- Brief body -- "

s.putBuf "select count(*)into existsCnt from " & VF(os.Name) & " where " & VF(os.Name) & "ID=a" & VF(os.Name) & "ID;"

s.putBuf "if existsCnt >0"

s.putBuf " then"

s.putBuf " aBRIEF:= " & VF(os.Name) & "_BRIEF_F(a" & VF(os.Name) & "id);"

s.putBuf "else"

s.putBuf " aBRIEF:= 'неверный идентификатор';"

s.putBuf "end if;"

s.putBuf " aBRIEF:=substr(aBRIEF,1,255);"

s.putBuf "return aBrief;"

s.putBuf "end; $$ language 'plpgsql';"

s.putBuf "GO"

 

o.Module = "--functions.Type.Body"

o.Block = "--" & ot.Name

o.OutNL s.getBuf

DebugOutput "POSTGRESGEN.CreateBriefProc:done "

Set s = Nothing

Exit Sub

bye:

'Resume

Set s = Nothing

End Sub

 

MS SQL

Private Sub CreateBriefProc(os As PART)

DebugOutput "SQLGEN.CreateBriefProc:start "

If os.PartType = PartType_Rassirenie Then

Exit Sub

End If

Dim st As PART

Set st = os

Dim chos As PART, i As Long, j As Long, f As FIELD

Dim s As Writer

Set s = New Writer

CreateBriefFunc os

log = log & vbCrLf & "-CreateBriefProc " & os.Name

On Error GoTo bye

s.putBuf ""

s.putBuf procDropSQL(os.Name & "_BRIEF")

s.putBuf "create proc " & os.Name & "_BRIEF ("

s.putBuf " @CURSESSION uniqueidentifier,"

s.putBuf " @" & os.Name & "id uniqueidentifier,"

s.putBuf " @BRIEF varchar(4000) output"

s.putBuf ") as " & " begin "

s.putBuf "set nocount on"

s.putBuf " declare @access int"

s.putBuf " declare @tmpStr varchar(255)"

s.putBuf " declare @tmpID uniqueidentifier"

s.putBuf " -- checking session --"

s.putBuf "if not exists( select 1 from the_session where the_sessionid=@cursession and closed=0 )"

s.putBuf " begin"

s.putBuf " raiserror('Сессия уже завершена.',16,1)"

s.putBuf " return"

s.putBuf " end"

s.putBuf " declare @Lang2 varchar(25)"

s.putBuf " select @Lang2=Lang from the_session where the_sessionid=@cursession"

s.putBuf "if @" & os.Name & "id is null begin set @BRIEF='' return end"

s.putBuf " -- Brief body -- "

s.putBuf "if exists(select 1 from " & os.Name & " where " & os.Name & "ID=@" & os.Name & "ID)"

s.putBuf " begin"

s.putBuf " -- verify access --"

s.putBuf " select @tmpID =SecurityStyleID from " & os.Name & " where " & os.Name & "id=@" & os.Name & "ID"

s.putBuf " exec CheckVerbRight cursession=@cursession,@Resource=@tmpID,@verb='BRIEF',@access=@access out "

s.putBuf " if @access=0 "

s.putBuf " begin"

s.putBuf " raiserror('No access for BRIEF Structure=" & os.Name & "',16,1)"

s.putBuf " return"

s.putBuf " end"

'''' 'MLF

's.putBuf " select BRIEF=dbo." & os.Name & "_BRIEF_F(@" & os.Name & "id)"

s.putBuf " select BRIEF=dbo." & os.Name & "_BRIEF_F(@" & os.Name & "id, @Lang2)"

 

s.putBuf "end else begin"

s.putBuf " set @BRIEF= 'неверный идентификатор'"

s.putBuf "end"

s.putBuf "set BRIEF=left(@BRIEF,4000)"

s.putBuf "end "

s.putBuf "go"

If (OptRights) Then

s.putBuf "revoke all on [dbo].[" & os.Name & "_BRIEF] to [public]"

s.putBuf "go"

s.putBuf "grant execute on [dbo].[" & os.Name & "_BRIEF] to [public]"

s.putBuf "go"

End If

o.Module = "--Procedures"

o.Block = "--TableProc"

o.OutNL s.getBuf

o.OutNL "GO"

DebugOutput "SQLGEN.CreateBriefProc:done "

Set s = Nothing

Exit Sub

bye:

log = log & vbCrLf & "ERROR-" & Err.Description & "<--ERROR"

'Resume

Set s = Nothing

End Sub

 

ORACLE

Private Sub CreateBriefProc(os As PART)

DebugOutput "ORAGEN.CreateBriefProc:start "

Dim st As PART

Set st = os

Dim chos As PART, i As Long, j As Long, f As FIELD

Dim s As Writer

Set s = New Writer

On Error GoTo bye

s.putBuf ""

s.putBuf "procedure " & VF(os.Name) & "_BRIEF ("

s.putBuf " aCURSESSION CHAR,"

s.putBuf " a" & VF(os.Name) & "id CHAR,"

s.putBuf " aBRIEF out varchar2"

s.putBuf ") as "

s.putBuf " aaccess integer;"

s.putBuf " atmpStr varchar2(255);"

s.putBuf " atmpID CHAR(38);"

s.putBuf " existsCnt integer;"

s.putBuf "begin"

s.putBuf " -- checking the_session --"

s.putBuf " select count(*) into existsCnt from the_session where the_sessionid=acursession and closed=0 ;"

s.putBuf "if existsCnt=0"

s.putBuf " then"

s.putBuf " raise_application_error(-20000,'Сессия уже завершена.');"

s.putBuf " return;"

s.putBuf " end if;"

 

s.putBuf "if a" & VF(os.Name) & "id is null then aBRIEF:=''; return; end if;"

 

s.putBuf " -- Brief body -- "

s.putBuf "select count(*)into existsCnt from " & VF(os.Name) & " where " & VF(os.Name) & "ID=a" & VF(os.Name) & "ID;"

s.putBuf "if existsCnt >0"

s.putBuf " then"

s.putBuf " -- verify access --"

s.putBuf " select SecurityStyleID into atmpid from " & VF(os.Name) & " where " & VF(os.Name) & "id=a" & VF(os.Name) & "ID;"

s.putBuf " CheckVerbRight (acursession=>acursession,aThe_Resource=>atmpID,averb=>'BRIEF',aaccess=>aaccess); "

s.putBuf " if aaccess=0 "

s.putBuf " then"

s.putBuf " raise_application_error(-20000,'No access for BRIEF Structure=" & VF(os.Name) & "');"

s.putBuf " return;"

s.putBuf " end if;"

s.putBuf " aBRIEF:=func." & VF(os.Name) & "_BRIEF_F(a" & VF(os.Name) & "id);"

s.putBuf "else"

s.putBuf " aBRIEF:= 'неверный идентификатор';"

s.putBuf "end if;"

s.putBuf " aBRIEF:=substr(aBRIEF,1,255);"

s.putBuf "end; "

 

o.Module = "--Procedures.Type.Body"

o.Block = "--" & ot.Name

o.OutNL s.getBuf

DebugOutput "ORAGEN.CreateBriefProc:done "

Set s = Nothing

Exit Sub

bye:

'Resume

Set s = Nothing

End Sub

 

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

Да, просто формируется набор процедур, в который упакована нужная логика, но эти процедуры идентеичны по названиям и параметрам! Поэтому уже на уровне слоя баз данных можно спокойно работать с рзными источниками. И более того можно разрабатывать приложения не очень задумываясь над тем, на какой реально базе данных оно будет работать. Хотя это конечно небольшое лукавство…

Иногда приходится писать что-то вроде этого:

f = f & " and invi_DEF_EndDate<=" & IIf(Session.IsMSSQL, MakeMSSQLDate(fltr.dtpinvi_DEF_EndDate_LE.Value), IIf(Session.IsORACLE, MakeORACLEDate(fltr.dtpinvi_DEF_EndDate_LE.Value), MakePGSQLDate(fltr.dtpinvi_DEF_EndDate_LE.Value)))

 

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

Ведущий рассылки: Михаил М. Баранов
http://www.abolsoft.ru bami@abolsoft.ru

В избранное