From ac9caf456f03e2cbc2fe2dfa6e19c31dbe0477f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=97=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=BE=D1=86=D0=BA=D0=B8=D0=B9?= Date: Tue, 24 Oct 2023 11:00:40 +0300 Subject: [PATCH] buffer-fix --- baseconnection.pas | 61 ++---- cgireport.pas | 97 +++++++++- commandcol.pas | 64 +++++++ connectionsdmunit.pas | 65 ++++++- exttypes.pas | 68 +++++-- lms_cgi.obj | Bin 0 -> 70388 bytes lmsreport.lpi | 13 ++ lmsreport.lpr | 7 +- maintcpserver.lfm | 97 +++++++--- maintcpserver.pas | 22 ++- reportdmunit.lfm | 6 + reportdmunit.pas | 75 +++++++- reports/allreportsunit.pas | 16 ++ reports/applicantlist.pas | 2 +- reports/applicantresult.pas | 455 ++++++++++++++++++++++++++++++++++++++++++++ tcpclient.pas | 7 +- tcpserver.pas | 3 +- tcpthreadhelper.pas | 87 +++++++-- 18 files changed, 1010 insertions(+), 135 deletions(-) create mode 100644 commandcol.pas create mode 100644 lms_cgi.obj create mode 100644 reports/allreportsunit.pas create mode 100644 reports/applicantresult.pas diff --git a/baseconnection.pas b/baseconnection.pas index f8c32f6..ea2cea0 100644 --- a/baseconnection.pas +++ b/baseconnection.pas @@ -24,6 +24,8 @@ type fisDone,fisFinished: boolean; fIsError: boolean; fSubClass: string; + function getInt(keyName: string;defaultValue: integer=0): integer; + function getString(keyName: string): string; public AccessTime: TDateTime; @@ -51,17 +53,6 @@ type end; TCommandClass=class of TCommand; - { TCommandCollection } - TCommandCollection=Class; - TCommandCollection=Class(TClassList) - private - class var fCollection: TCommandCollection; - public - class procedure Register(ACommand: TCommandClass); - class function Find(Action,SubClass: string): TCommandClass; - class procedure Init; - class procedure Done; - end; TBaseConnection=class(TThread) private @@ -116,6 +107,8 @@ type end; implementation +uses + commandcol; { TBaseConnection } procedure TBaseConnection.Init; @@ -328,44 +321,19 @@ begin end; -{ TCommandCollection } - -class procedure TCommandCollection.Register(ACommand: TCommandClass); -begin - fCollection.Add(ACommand); -end; - -class function TCommandCollection.Find(Action, SubClass: string): TCommandClass; -var - i: integer; -begin - for i := 0 to fCollection.Count-1 do - if fCollection.Items[i].InheritsFrom(TCommand) and SameText(Action, TCommandClass(fCollection.Items[i]).CommandName) and SameText(SubClass, TCommandClass(fCollection.Items[i]).CommandSubClass) then - begin - result := TCommandClass(fCollection.Items[i]) ; - exit; - end; - for i := 0 to fCollection.Count-1 do - if fCollection.Items[i].InheritsFrom(TCommand) and SameText(Action, TCommandClass(fCollection.Items[i]).CommandName) and SameText('', TCommandClass(fCollection.Items[i]).CommandSubClass) then - begin - result := TCommandClass(fCollection.Items[i]) ; - exit; - end; - result := nil; -end; - -class procedure TCommandCollection.Init; -begin - fCollection := TCommandCollection.Create; -end; - -class procedure TCommandCollection.Done; -begin - fCollection.Free; -end; { TCommand } +function TCommand.getInt(keyName: string; defaultValue: integer): integer; +begin + result := StrToIntDef(fResult.Keys.Values[keyName],defaultValue); +end; + +function TCommand.getString(KeyName: string): string; +begin + result := fResult.Keys.Values[KeyName]; +end; + constructor TCommand.Create(aConnect: TBaseConnection; ASubClass: string); begin fconnect := AConnect; @@ -425,6 +393,5 @@ procedure TCommand.Log(ALevel: TLogLevel; msg: string); begin connect.log(ALevel,self, self.CommandID+#09+msg) end; - end. diff --git a/cgireport.pas b/cgireport.pas index 9bd0c0b..efa5341 100644 --- a/cgireport.pas +++ b/cgireport.pas @@ -29,11 +29,13 @@ type function Run: boolean; override; function ParseArguments(Args: TStrings; out Errors: TStrings): boolean; override; function ProcessOptionValues(ParamName: string; out Answer: string; out RetValue: QWORD; out OptionValues: TStrings): boolean; override; + procedure EditTemplate; + procedure FillDefaults; end; implementation uses - cgiDM,reportDMUnit, types, strutils, LazUTF8; + cgiDM,reportDMUnit, types, strutils, LazUTF8,allreportsunit,commandcol; { TReportCommand } procedure TReportCommand.CreateVariablesTable; @@ -106,6 +108,13 @@ begin end; procedure TReportCommand.FillVars; +const + Q_varlist= + 'select coalesce(v1.name,v0.name) as name,coalesce(v1.query,v0.query) as query '+ + 'from xp_report_cgi c '+ + ' left join xp_report_variables v0 on v0.name=any(c.variables) and v0.xp_rpt_id=0 and v0.var_type=%1:d '+ + ' left join xp_report_variables v1 on v1.name=any(c.variables) and v1.xp_rpt_id=c.xp_rpt_id and v0.var_type=%1:d '+ + 'where c.xp_rpt_id=%0:d and coalesce(v1.query,v0.query,'''')<>'''' '; var ASQL: string; q: string; @@ -115,7 +124,7 @@ var begin log(mtDebug,'FillVars'); script := format('insert into tmp_report_variables(name,value_string,var_type) values(''UserName'',%s,0); ',[TNIDBDM.StringAsSQL(self.Connect.User)]); - ASQL := format('select name,query from xp_report_variables where xp_rpt_id=%d and var_type=0',[ReportID]); + ASQL := format(Q_varlist,[ReportID,0]); with connect.Processor.GetData(ASQL) do try while not eof do @@ -136,7 +145,7 @@ begin finally free; end; - ASQL := format('select name,query from xp_report_variables where xp_rpt_id=%d and var_type=1',[ReportID]); + ASQL := format(Q_varlist,[ReportID,1]); with connect.Processor.GetData(ASQL) do try while not eof do @@ -184,7 +193,7 @@ begin end; ReportTitle := connect.Processor.QueryValue(format('select r.name from xp_report_cgi g join xp_report r on r.xp_rpt_id=g.xp_rpt_id where cgi_name=%s',[TNidbDM.StringAsSQL(ReportName)])); CreateVariablesTable; - log(mtInfo,'Построение отчета '+ReportTitle); + log(mtInfo,'Построение отчета '+ReportTitle); connect.ReportProcessor.RecordID:=ReportID; fcurrentStage := 'исполняется (подготовка)'; try @@ -217,7 +226,7 @@ begin exit; end; end; - fResult := TCommandData.Create(0,fileData.size,ReportTitle+'.pdf',nil,[],fileData); + fResult := TCommandData.Create(0,fileData.size,ReportTitle+'.pdf',['type=application/pdf'],[],fileData); fileData.Seek(0,soFromBeginning); result := true; finally @@ -246,7 +255,7 @@ begin 'join xp_report_params p on p.xp_rpt_id=c.xp_rpt_id and coalesce(p.required,true) '+ 'where c.cgi_name=%s and p.name not in (%s) '+ 'order by fill_order,p.name ', - [TNIDBDM.StringAsSQL(ReportName),TNIDBDM.StringAsSQL(ids)]); + [TNIDBDM.StringAsSQL(ReportName),(ids)]); with Connect.Processor.GetData(asql) do try if not eof then @@ -329,8 +338,80 @@ begin result := true; end; +procedure TReportCommand.EditTemplate; +begin + CreateVariablesTable; + log(mtInfo,'Построение отчета '+ReportTitle); + connect.ReportProcessor.RecordID:=ReportID; + fcurrentStage := 'исполняется (подготовка)'; + try + Prepare; + except on e: Exception do + begin + connect.Processor.LogError(self,e,'prepare'); + fResult := TCommandData.Create(ErrorInternal,0,'Ошибка составления',nil,[],nil); + exit; + end; + end; + fcurrentStage := 'исполняется (настройка)'; + try + FillVars; + PrepareVars; + except on e: Exception do + begin + connect.Processor.LogError(self,e,'vars'); + fResult := TCommandData.Create(ErrorInternal,0,'Ошибка составления',nil,[],nil); + exit; + end; + end; + fcurrentStage := 'исполняется ()'; + try + connect.ReportProcessor.EditReport(@OnFillVariables); + except on e: Exception do + begin + connect.Processor.LogError(self,e,'ExportReport'); + fResult := TCommandData.Create(ErrorInternal,0,'Ошибка составления',nil,[],nil); + exit; + end; + end; +end; + +procedure TReportCommand.FillDefaults; +var + asql: string; + l,e: TStrings; +begin + asql := format( + 'select name, '+ + 'case '+ + ' when type IN (1,2,3,6,17) then ''0'' '+ + ' when type=4 then ''2020-02-02'' '+ + ' when type=5 then ''2020-02-02 20:20:02'' '+ + ' when type=0 then '''' '+ + 'end as value '+ + 'from xp_report_params p '+ + 'where p.xp_rpt_id=%d ', + [ReportID]); + l := TStringList.Create; + try + l.add('name='+ReportName); + with Connect.Processor.GetData(asql) do + try + while not eof do + begin + l.Add(format('%s=%s',[fieldbyname('name').asString,fieldbyname('value').asString])); + Next; + end; + finally + free; + end; + ParseCommand(-1,0,ReportName,l,[],nil,e); + finally + l.free; + end; +end; + + -Initialization - TCommandCollection.Register(TReportCommand); end. diff --git a/commandcol.pas b/commandcol.pas new file mode 100644 index 0000000..6de91ec --- /dev/null +++ b/commandcol.pas @@ -0,0 +1,64 @@ +unit commandcol; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils,Contnrs,baseconnection; +type + { TCommandCollection } + TCommandCollection=Class; + TCommandCollection=Class(TClassList) + private + class var fCollection: TCommandCollection; + public + class procedure Register(ACommand: TCommandClass); + class function Find(Action,SubClass: string): TCommandClass; + class procedure Init; + class procedure Done; + end; + +implementation +{ TCommandCollection } + +class procedure TCommandCollection.Register(ACommand: TCommandClass); +begin + fCollection.Add(ACommand); +end; + +class function TCommandCollection.Find(Action, SubClass: string): TCommandClass; +var + i: integer; +begin + for i := 0 to fCollection.Count-1 do + if fCollection.Items[i].InheritsFrom(TCommand) and SameText(Action, TCommandClass(fCollection.Items[i]).CommandName) and SameText(SubClass, TCommandClass(fCollection.Items[i]).CommandSubClass) then + begin + result := TCommandClass(fCollection.Items[i]) ; + exit; + end; + for i := 0 to fCollection.Count-1 do + if fCollection.Items[i].InheritsFrom(TCommand) and SameText(Action, TCommandClass(fCollection.Items[i]).CommandName) and SameText('', TCommandClass(fCollection.Items[i]).CommandSubClass) then + begin + result := TCommandClass(fCollection.Items[i]) ; + exit; + end; + result := nil; +end; + +class procedure TCommandCollection.Init; +begin + fCollection := TCommandCollection.Create; +end; + +class procedure TCommandCollection.Done; +begin + fCollection.Free; +end; +initialization + TCommandCollection.Init; +finalization + TCommandCollection.Done; + +end. + diff --git a/connectionsdmunit.pas b/connectionsdmunit.pas index ca65bd5..9a3e315 100644 --- a/connectionsdmunit.pas +++ b/connectionsdmunit.pas @@ -45,12 +45,14 @@ type function ProcessReports(out rValues: TStrings): boolean; function ProcessOptionValues(ReportName,ParamName: string; ParamValues: TStrings; out Answer: string; out RetValue: QWORD; out OptionValues: TStrings): boolean; procedure LoadConfig; + public property DataHost: string read fDataHost; property DataPort: integer read fDataPort; property DataBase: string read fDataBase; property Logger: TEventLog read fLogger write fLogger; procedure Log(ALevel: TLogLevel; Sender: TObject; msg: string); + procedure InitBaseCon; procedure Start; procedure Stop; procedure Idle(Sender: TObject); @@ -59,6 +61,8 @@ type const CommandID:DWORD; const Param:QWord; const ACommand: string; const Fields: TStrings; const iParams: TParamArray; const Data: TStream; out Code: DWORD; out RetValue: QWord; out Answer: string; out rValues: TStrings; out iValues: TParamArray; out ByteData: TStream ): boolean; constructor CreateWithLog(ALogger: TEventLog); + procedure FillTemplates(RepList: TStrings); + procedure EditTemplate(ReportID: integer); end; var @@ -66,7 +70,7 @@ var implementation uses - xpUtilUnit, strutils, xpAccessUnit, inifiles; + xpUtilUnit, strutils, xpAccessUnit, inifiles,commandcol, cgiReport; {$R *.lfm} @@ -338,6 +342,53 @@ begin inherited Create(nil); end; +procedure TConnectionsDM.FillTemplates(RepList: TStrings); +var + asql: string; +begin + asql := + 'select r.xp_rpt_id,r.name, c.cgi_name from xp_report r '+ + ' join xp_report_cgi c on c.xp_rpt_id=r.xp_rpt_id '+ + 'order by r.name '; + with MainCon.GetData(asql) do + try + while not eof do + begin + RepList.AddObject(format('%s (%s)',[fieldbyname('name').asString, FieldByName('cgi_name').asString]),TObject(ptrint(fieldbyname('xp_rpt_id').asInteger))); + next; + end; + finally + free; + end; +end; + +procedure TConnectionsDM.EditTemplate(ReportID: integer); +var + asql: string; + RName: string; + con: TBaseConnection; + cc: TCommandClass; + cmd: TReportCommand; +begin + asql := format('select cgi_name from xp_report_cgi where xp_rpt_id=%d',[ReportID]); + RName := MainCon.QueryValue(asql); + con := NewConnection; + try + cc := TCommandCollection.Find('report',RName); + cmd := cc.Create(con,RName) as TReportCommand; + try + cmd.ReportID := ReportID; + cmd.ReportName:=RName; + cmd.FillDefaults; + cmd.EditTemplate; + finally + cmd.free; + end; + finally + con.Free; + end; +end; + function TConnectionsDM.ProcessLogin(UserName, UserPassword: string; out UserID: integer): boolean; var ASQL: string; @@ -504,13 +555,17 @@ begin end; end; -procedure TConnectionsDM.Start; +procedure TConnectionsDM.InitBaseCon; begin MainCon.connection.RemoteHost:=DataHost; MainCon.connection.RemotePort:=DataPort; MainCon.connection.Database:=DataBase; MainCon.OpenConnection; - //Input.OnIdle:=@Idle; +end; + +procedure TConnectionsDM.Start; +begin + InitBaseCon;//Input.OnIdle:=@Idle; Input.Start; fRunning:=true; end; @@ -534,9 +589,5 @@ begin TBaseConnection(conList[i]).SetIdle; end; -initialization - TCommandCollection.Init; -finalization - TCommandCollection.Done; end. diff --git a/exttypes.pas b/exttypes.pas index 585d70d..981021f 100644 --- a/exttypes.pas +++ b/exttypes.pas @@ -43,8 +43,10 @@ type fReadReady,fWriteReady: TSimpleEvent; fClosed: boolean; cs: TCriticalSection; + fLogger: TLogger; + procedure log(msg: string); public - constructor Create(BufferSize: integer); + constructor Create(ALogger: TLogger; BufferSize: integer); destructor Destroy; override; function Push(const data; datasize: integer): integer; function Pop(var data; datasize: integer): integer; @@ -57,6 +59,9 @@ type property ReadReady: TSimpleEvent read fReadReady; property WriteReady: TSimpleEvent read fWriteReady; end; + + { TCommandData } + TCommandData=class Code:DWORD; Param:QWord; @@ -64,7 +69,8 @@ type Keys: TStrings; iValues: TParamArray; Data: TStream; - constructor Create(ACode:DWORD;AParam:QWord; AName: string; AKeys: TStrings; AValues: TParamArray; AData: TStream); + constructor Create(ACode:DWORD;AParam:QWord; AName: string; AKeys: TStrings; AValues: TParamArray; AData: TStream); overload; + constructor Create(ACode:DWORD;AParam:QWord; AName: string;const AKeys: Array of string; AValues: TParamArray; AData: TStream); overload; destructor Destroy; override; procedure AssignTo(out ACode:DWORD;out AParam:QWord; out AName: string; out AKeys: TStrings; out AValues: TParamArray; out AData: TStream); end; @@ -160,10 +166,15 @@ end; { TRoundBuffer } +procedure TRoundBuffer.log(msg: string); +begin + {$IFDEF DEBUG} if assigned(fLogger) then fLogger(mtExtra,self,msg); {$ENDIF} +end; -constructor TRoundBuffer.Create(BufferSize: integer); +constructor TRoundBuffer.Create(ALogger: TLogger; BufferSize: integer); begin inherited Create; + flogger := ALogger; cs := TCriticalSection.Create; SetLength(self.intdata,BufferSize); fSize:=BufferSize; @@ -203,6 +214,7 @@ begin fWriteReady.WaitFor(INFINITE); if fClosed then exit; delta := 0; + log(format('Push size=%d, R=%d, W=%d ',[fDataSize,ptrRead,ptrWrite])); while not fClosed and (rem>0) and (fDataSize+delta0) and (delta>0) do + delta := 0; + log(format('Pop size=%d, R=%d, W=%d ',[fDataSize,ptrRead,ptrWrite])); + while not fClosed and (rem>0) and (fDataSize-delta>0) do begin p^:=intData[i]; s := s + inttohex(intData[i],2)+' '; inc(p); i := (i+1) mod fSize; - dec(delta); + inc(delta); dec(rem); end; cs.Enter; ptrRead := i; - fDataSize:=delta; + dec(fDataSize,delta); if fDataSize=0 then + begin fReadReady.ResetEvent; + log('buffer empty'); + end; cs.Leave; - result := datasize-rem; + result := delta; + log(format('Pop %d bytes size=%d',[result,fDataSize])); fWriteReady.SetEvent; end; @@ -281,6 +303,7 @@ begin begin result := -1; fReadReady.SetEvent; + fWriteReady.SetEvent; end; end; @@ -384,6 +407,27 @@ begin Data := nil; end; +constructor TCommandData.Create(ACode: DWORD; AParam: QWord; AName: string; + const AKeys: array of string; AValues: TParamArray; AData: TStream); +var + l: TStrings; + i: integer; +begin + if length(AKeys)=0 then + Create(ACode,AParam,AName,nil,AValues,AData) + else + begin + l := TStringList.Create; + try + for i := low(AKeys) to high(AKeys) do + l.add(AKeys[i]); + Create(ACode,AParam,AName,l,AValues,AData) + finally + l.free; + end; + end; +end; + destructor TCommandData.Destroy; begin if assigned(Keys) then Keys.Free; diff --git a/lms_cgi.obj b/lms_cgi.obj new file mode 100644 index 0000000000000000000000000000000000000000..839e54a48157941e620d79d29a9a5a40bee5e1e8 GIT binary patch literal 70388 zcmeEv2|QI@_y4)Zo0*6tQ<7wiP-KdbLL#EU6jG#+A>~SmO36HAXb>qyqsnlLP>2Sl zR5ThYDP<^i|LdG{k1kL5eV%uCp7;I#KF{jz^WAIhwbx#IJo}uxuRNkj2uja{NC+Nd zBdKf-*zAr;@k=ou3DJY-J3y4yLNG)SDgXnqWBGLugu(*S7*>SB4(P~^P`FSYkOwf0 z8=(L&kT{l4jD+V?17w9%b3isg7eLfb2p|VwJRm1v0U#NW3OE6<6%Z|_50D#>%z~FE z4af_;4j><(4In?DH=qC@I7<-(Oal}GEC!qiSO+K!*a0X4I0QHePyp&B3a9`m251O4 z8PE|>9MB&SfPwHf8$ykaAzV&I3mHSWT#Siafr(s)iQJrt+>wdgn~6Mxi9CjhJdKIG zfQh`6iM)=9yp@T(i-~-QiJXjf?iixCKNGnE6S)o(xj7TLBNMqd6L|;|dCWLDI>r@v zVQ#P?6fHn$KwCf^z|DX*fYE^7fQJBKl2OhArU5ns76ZNjtONWC*a0|+51|YJDgp|? zJYEQ>0O$m02sozsu-OdrX`EjOU0%RMUdlvX$3))BMBc?jKEy;$MknWx3NvpWCY2z&z61PlSR0gMOq11tdC4oC$|0c-_44cG@* z4M>Lj)?+|vz;}Q;fE*LS2B0*cH=qt6+6IG(VN~FqR$g1as?)G9VT*fCUQq6a&IQ`5GL{%Ch{~U@&YFEQYP{`Ch}G$ z@-8OwAtrJ%y4e^*^!8^WS70L7VIntYB6nmW_hupwVIq%VB2Qx?FJK}sWg@R*B5!3P z?_we!Vj?G_o4heZZ+|9o1txMGCUSEoaz`d|Zzl4PadLFeZ3F8PnFpb)1e6B!0n`Bu z0ki>(2J{9@0)$OE4ZUZ1K>>y^0EWobD zh%I7`I6#Ui#Q4XgnIewB+k)N#Kx>Fi7?W;^SOCv8Mz$PereKedE`i*ZAi+!Hqw5k< zwA97OLby6YAdjY+LJakXn=Y|OtPm%#Yy7lCuI+|@^43C|S=8^g^W!)c7+G{{?%0A(U*F35|aU*jo=Vu@$|9!6#23G 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 #2bCdbc_f``cujB^C%dj2gcoR^3 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 literal 0 HcmV?d00001 diff --git a/lmsreport.lpi b/lmsreport.lpi index 012abcb..96faebe 100644 --- a/lmsreport.lpi +++ b/lmsreport.lpi @@ -125,6 +125,18 @@ + + + + + + + + + + + + @@ -135,6 +147,7 @@ + diff --git a/lmsreport.lpr b/lmsreport.lpr index 78a3b0c..5bd04a5 100644 --- a/lmsreport.lpr +++ b/lmsreport.lpr @@ -10,9 +10,10 @@ uses athreads, {$ENDIF} Interfaces, // this includes the LCL widgetset - sysutils,Forms, abbrevia, lnetbase, MainTcpServer, tcpthreadhelper, reportDMUnit, - ConnectionsDmUnit, cgiDM, xpAccessUnit, extTypes, tcpserver, tcpClient, -cgiReport, cgiCommand, fr_utils, baseconnection + sysutils, Forms, abbrevia, lnetbase,commandcol,baseconnection, MainTcpServer, tcpthreadhelper, + reportDMUnit, ConnectionsDmUnit, cgiDM, xpAccessUnit, extTypes, tcpserver, + tcpClient, cgiReport, cgiCommand, fr_utils, applicantresult, + allreportsunit { you can add units after this }; {$R *.res} diff --git a/maintcpserver.lfm b/maintcpserver.lfm index 4e696b4..15dba4a 100644 --- a/maintcpserver.lfm +++ b/maintcpserver.lfm @@ -1,14 +1,14 @@ object CGIServerGUI: TCGIServerGUI Left = 333 - Height = 309 + Height = 566 Top = 224 Width = 870 Caption = 'Сервер отчетов LMS' - ClientHeight = 309 + ClientHeight = 566 ClientWidth = 870 OnCreate = FormCreate OnDestroy = FormDestroy - LCLVersion = '2.2.0.4' + LCLVersion = '2.2.4.0' object Panel1: TPanel Left = 0 Height = 50 @@ -40,19 +40,19 @@ object CGIServerGUI: TCGIServerGUI end object GroupBox1: TGroupBox Left = 0 - Height = 259 + Height = 246 Top = 50 Width = 368 Align = alLeft Caption = 'Запрос' - ClientHeight = 240 - ClientWidth = 366 + ClientHeight = 226 + ClientWidth = 364 TabOrder = 1 object Keys: TMemo Left = 0 - Height = 210 - Top = 30 - Width = 366 + Height = 203 + Top = 23 + Width = 364 Align = alClient Lines.Strings = ( 'user=nnz' @@ -62,11 +62,11 @@ object CGIServerGUI: TCGIServerGUI end object edtRequest: TComboBox Left = 0 - Height = 30 + Height = 23 Top = 0 - Width = 366 + Width = 364 Align = alTop - ItemHeight = 0 + ItemHeight = 15 ItemIndex = 3 Items.Strings = ( 'version' @@ -86,46 +86,46 @@ object CGIServerGUI: TCGIServerGUI end object GroupBox2: TGroupBox Left = 373 - Height = 259 + Height = 246 Top = 50 Width = 497 Align = alClient Caption = 'Ответ' - ClientHeight = 240 - ClientWidth = 495 + ClientHeight = 226 + ClientWidth = 493 TabOrder = 2 object edtAnswer: TEdit Left = 0 - Height = 30 + Height = 23 Top = 25 - Width = 495 + Width = 493 Align = alTop OnDblClick = edtAnswerDblClick TabOrder = 0 end object retValues: TMemo Left = 0 - Height = 75 - Top = 85 - Width = 495 + Height = 105 + Top = 71 + Width = 493 Align = alClient TabOrder = 1 end object intValues: TListBox Left = 0 - Height = 80 - Top = 160 - Width = 495 + Height = 50 + Top = 176 + Width = 493 Align = alBottom + Columns = 4 ItemHeight = 0 TabOrder = 2 - TopIndex = -1 end object edtQValue: TEdit Left = 0 - Height = 30 - Top = 55 - Width = 495 + Height = 23 + Top = 48 + Width = 493 Align = alTop TabOrder = 3 end @@ -133,15 +133,54 @@ object CGIServerGUI: TCGIServerGUI Left = 0 Height = 25 Top = 0 - Width = 495 + Width = 493 Align = alTop TabOrder = 4 end end object Splitter1: TSplitter Left = 368 - Height = 259 + Height = 246 Top = 50 Width = 5 end + object GroupBox3: TGroupBox + Left = 0 + Height = 270 + Top = 296 + Width = 870 + Align = alBottom + Caption = 'Шаблоны' + ClientHeight = 250 + ClientWidth = 866 + TabOrder = 4 + object ReportsPanel: TPanel + Left = 0 + Height = 50 + Top = 200 + Width = 866 + Align = alBottom + ClientHeight = 50 + ClientWidth = 866 + TabOrder = 0 + object EditTemplate: TButton + Left = 760 + Height = 25 + Top = 11 + Width = 100 + Caption = 'Шаблон' + OnClick = EditTemplateClick + TabOrder = 0 + end + end + object ReportsList: TListBox + Left = 0 + Height = 200 + Top = 0 + Width = 866 + Align = alClient + ItemHeight = 0 + TabOrder = 1 + end + end end diff --git a/maintcpserver.pas b/maintcpserver.pas index fa775cb..95be83e 100644 --- a/maintcpserver.pas +++ b/maintcpserver.pas @@ -17,12 +17,16 @@ type { TCGIServerGUI } TCGIServerGUI = class(TForm) + EditTemplate: TButton; edtAnswer: TEdit; edtQValue: TEdit; edtRequest: TComboBox; GroupBox1: TGroupBox; GroupBox2: TGroupBox; + GroupBox3: TGroupBox; intValues: TListBox; + ReportsList: TListBox; + ReportsPanel: TPanel; StatusPanel: TPanel; retValues: TMemo; Keys: TMemo; @@ -30,6 +34,7 @@ type Panel1: TPanel; Splitter1: TSplitter; StartButton: TButton; + procedure EditTemplateClick(Sender: TObject); procedure edtAnswerDblClick(Sender: TObject); procedure SendButtonClick(Sender: TObject); procedure StartButtonClick(Sender: TObject); @@ -66,11 +71,13 @@ uses procedure TCGIServerGUI.FormCreate(Sender: TObject); begin fLogger := TEventLog.Create(self); + fLogger.Active:=false; fLogger.LogType:=TLogType.ltFile; fLogger.FileName:=ChangeFileExt(paramstr(0),'.log'); flogger.Identification:='LMS-Report-Test'; - fLogger.Active:=false; fLogger.Active:=true; + flogger.Info('TCGIServerGUI.FormCreate'); + Server := TConnectionsDM.CreateWithLog(fLogger); ConnectionsDM := Server; cmdDone := true; @@ -98,10 +105,19 @@ begin Keys.Lines.Add('='+edtanswer.text); end; +procedure TCGIServerGUI.EditTemplateClick(Sender: TObject); +var + rID: integer; +begin + rID := PtrInt(ReportsList.Items.Objects[ReportsList.ItemIndex]); + Server.EditTemplate(rID); +end; + procedure TCGIServerGUI.StartButtonClick(Sender: TObject); begin Server.Start; + Server.FillTemplates(ReportsList.Items); started := true; Panel1.Caption := 'запущен'; SendButton.Enabled := true; @@ -169,7 +185,7 @@ begin if Assigned(Data) then begin Data.seek(0,soFromBeginning); - fs := TFileStream.Create(Answer,fmCreate); + fs := TFileStream.Create('out/'+Answer,fmCreate); try fs.CopyFrom(Data,Data.size); finally @@ -177,7 +193,7 @@ begin end; end; finally - Sender.Terminate; + Sender.SetComplete; cmdDone := true; end; diff --git a/reportdmunit.lfm b/reportdmunit.lfm index 0965e17..3335fab 100644 --- a/reportdmunit.lfm +++ b/reportdmunit.lfm @@ -91,4 +91,10 @@ object ReportDM: TReportDM Left = 180 Top = 86 end + object AbZipper1: TAbZipper + AutoSave = False + DOSMode = False + Left = 186 + Top = 155 + end end diff --git a/reportdmunit.pas b/reportdmunit.pas index 870d4e6..479c806 100644 --- a/reportdmunit.pas +++ b/reportdmunit.pas @@ -6,7 +6,7 @@ interface uses Classes, SysUtils, frxClass, frxExportPDF, frxExportODF, - xpMemParamManagerUnit, AbUnzper, frxDBSet, cgiDM,extTypes; + xpMemParamManagerUnit, AbUnzper, AbZipper, frxDBSet, cgiDM,extTypes; type TExportFileType = (ftPDF,ftRTF,ftXLS);//(ftPDF,ftMail,ftRTF,ftXLS,ftHTML); @@ -39,6 +39,7 @@ type TReportDM = class(TDataModule) AbUnZipper1: TAbUnZipper; + AbZipper1: TAbZipper; frxODSExport1: TfrxODSExport; frxODTExport1: TfrxODTExport; frxPDFExport1: TfrxPDFExport; @@ -80,12 +81,13 @@ type procedure LoadVariables(AVariables, AParam : TxpMemParamManager); procedure OnMasterRecord(Sender: TObject); procedure LoadReportTemplate(); + procedure SaveReportTemplate(); procedure CopyReportVariables(AVariables, AParam: TxpMemParamManager); public RecordID: integer; NidbData: TNIDBDM; procedure ExportReport( ExportType: TExportFileType; Data: TStream; OnStage: TLogger; OnVars: TVariableFillProc); - + procedure EditReport(OnVars: TVariableFillProc); end; var @@ -595,6 +597,28 @@ begin end; // try end; +procedure TReportDM.SaveReportTemplate; +var + ReportStream : TMemoryStream; + BlobStream : TStream; + ASQL: string; +begin + NidbData.log(mtDebug,self,'ExportReport.TemplateArh'); + ReportStream := TMemoryStream.Create; + BlobStream := TMemoryStream.Create; + try + frxReport.SaveToStream(ReportStream); + ReportStream.seek(0,soFromBeginning); + PackReport(ReportStream,BlobStream,AbZipper1); + ASQL := format('update xp_report set TemplateArh=%s where xp_rpt_id=%d',[TNIDBDM.StreamAsSQL(BlobStream),RecordID]); + NidbData.ExecuteSQL(ASQL); + finally + ReportStream.Free; + BlobStream.Free; + end; // try +end; + + procedure TReportDM.CopyReportVariables(AVariables, AParam: TxpMemParamManager); var i: integer; @@ -706,6 +730,53 @@ begin NidbData.log(mtDebug,self,'Report complete'); end; +procedure TReportDM.EditReport(OnVars: TVariableFillProc); +var + I : Integer; + flt : TfrxCustomExportFilter; + v : Variant; + AVariables, AParam: TxpMemParamManager; +begin + fOnVars:=OnVars; + frxReport.EngineOptions.EnableThreadSafe:=true; + NidbData.log(mtDebug,self,'EditReport'); + ReportQueries := TReportQuery.Create; + AVariables := TxpMemParamManager.Create; + AParam := TxpMemParamManager.Create; + try + LoadQueries; + LoadDefaultVariables(AVariables); + LoadLogos(AVariables); + LoadVariables(AVariables,AParam); + if assigned(fOnVars) then fOnVars(AVariables); + frxReport.EngineOptions.DestroyForms := False; + // Создаём источники данных + CreateDBDataSet(ReportQueries); + LoadReportTemplate; + CopyReportVariables(AVariables,AParam); + TxpFRFunctions.SetReport(NidbData,AVariables); + + try + frxReport.DesignReport; + if frxReport.Modified then + SaveReportTemplate(); + except on e: Exception do + begin + NidbData.logError(self,e,'frxReport.PrepareReport'); + raise; + end; + end; + + + finally + ReportQueries.Free; + AVariables.Free; + AParam.Free; + end; + NidbData.log(mtDebug,self,'Report complete'); +end; + + end. diff --git a/reports/allreportsunit.pas b/reports/allreportsunit.pas new file mode 100644 index 0000000..f10afed --- /dev/null +++ b/reports/allreportsunit.pas @@ -0,0 +1,16 @@ +unit allreportsunit; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils,commandcol; + +implementation +uses + cgiReport,applicantlist,applicantresult; +initialization +TCommandCollection.Register(TReportCommand); +end. + diff --git a/reports/applicantlist.pas b/reports/applicantlist.pas index 58ee617..8ee657f 100644 --- a/reports/applicantlist.pas +++ b/reports/applicantlist.pas @@ -23,7 +23,7 @@ type implementation uses - cgiDM,dateutils,baseconnection; + cgiDM,dateutils,commandcol; { TRepApplicantList } class function TRepApplicantList.CommandSubClass: string; diff --git a/reports/applicantresult.pas b/reports/applicantresult.pas new file mode 100644 index 0000000..6324c1a --- /dev/null +++ b/reports/applicantresult.pas @@ -0,0 +1,455 @@ +unit applicantresult; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, cgiReport,xpMemParamManagerUnit; +const + ApplicantExtraParamCnt=4; + ApplicantExtraFields: array[1..ApplicantExtraParamCnt] of string = ('tabel_mode','olympic_mode','portfolio_mode','techno_mode'); +type + + { TRepApplicantList } + + { TRepApplicantResult } + + TRepApplicantResult=class(TReportCommand) + private + edtMode: integer; + cbStream: integer; + idYear: integer; + ColNames: array[1..12] of string; + ColCount: integer; + function MakeCols(): string; + function UpdateEnrollStatus(): integer; + public + class function CommandSubClass: string; override; + procedure Prepare; override; + procedure OnFillVariables(AVariables: TxpMemParamManager); override; + end; + +implementation +uses + cgiDM,dateutils,commandcol; + +{ TRepApplicantResult } + +function TRepApplicantResult.MakeCols: string; +var + SQL: string; + i,j: integer; + exams: string; + l: TStrings; +begin + exams := ''; + for i := 1 to 6 do + exams := exams + format( + 'UNION SELECT COALESCE(NULLIF(Exam%0:dColName,''''),EnterExam%0:dName) as ExamName, coalesce(d.sorting, %0:d) as sorting '+ + 'FROM applicant_group g '+ + ' LEFT JOIN (SELECT d.school_year, COALESCE(d.stream,0) as stream, l.name as exam_name, d.sorting,d.kurs '+ + ' FROM enroll_disciplines d '+ + ' JOIN lessons l ON l.lid=d.lid '+ + ' WHERE d.school_year=%1:d AND COALESCE(d.stream,0)=%2:d '+ + ' ) d ON d.school_year=g.school_year AND d.stream=COALESCE(g.stream) AND d.exam_name=g.EnterExam%0:dName '+ + 'WHERE g.school_year=%1:d AND (%2:d=COALESCE(g.stream,0)) '+ + 'AND NULLIF(EnterExam%0:dName,'''') IS NOT NULL '+ + 'AND EXISTS (SELECT 1 FROM xp_applicant a WHERE a.applicant_group=g.id AND a.Child_Class=coalesce(d.kurs,a.Child_Class) ) ', + [i,idYear,cbStream]); + SQL := format( + 'DROP TABLE IF EXISTS tmpExams; '+ + 'CREATE TEMPORARY TABLE tmpExams AS '+ + 'SELECT trim(t.ExamName) as ExamName, COALESCE(NULLIF(FIND_IN_SET(trim(t.ExamName),''Русский язык,Математика,Иностранный язык''),0),min(t.sorting)+6 ) as sorting FROM ( '+ + ' SELECT '''' as ExamName, 0 as sorting '+ + ' %2:s '+ + ' ) t '+ + 'WHERE t.sorting>0 '+ + 'GROUP BY t.ExamName; ', + [idYear,cbStream,Exams]); + connect.processor.ExecuteSQL(SQL); + + ColCount := 0; + with connect.processor.getData('SELECT ExamName,sorting FROM tmpExams ORDER BY sorting,ExamName ') do + try + while not eof and (ColCount<8) do + begin + if (pos('физ',AnsiLowerCase(FieldByName('ExamName').AsString))=0) or (pos('физика',AnsiLowerCase(FieldByName('ExamName').AsString))>0) then + begin + inc(ColCount); + ColNames[ColCount] := FieldByName('ExamName').AsString; + end; + Next; + end; + finally + Free; + end; + result := ''; + for I := 1 to ColCount do + begin + result := result + 'CASE '; + for j := 1 to 6 do + result := result + format( + ' WHEN a1.Col%0:d = %1:s THEN a1.Grade%0:d ', + [j,TNIDBDM.StringAsSQL(ColNames[i])]); + result := result + format(' ELSE NULL END AS Exam%d, ',[i]); + end; + for I := ColCount+1 to 12 do + begin + result := result+ Format('NULL AS Exam%d, ',[i]); + end; +end; + + +function TRepApplicantResult.UpdateEnrollStatus: integer; +var + SQL: string; + separate_enroll: boolean; + Z2: string; + ColSorter: string; + I,j: Integer; + SParts: array[1..10] of string; + DateFilter: string; + FS:TStrings; +begin + DateFilter := ''; + if cbStream > 0 then + DateFilter := ' AND cast(a1.Date as DATE) BETWEEN %10:s AND %11:s '; + + for i := 1 to 10 do + sParts[i] := ''; + for i := 1 to 8 do + begin + sParts[1] := sParts[1] + format( + '+IF(COALESCE(ep.psycho_mode%0:d,0)>=0,gradevalue((COALESCE(ep.psycho_mode%0:d,0)+1)*a1.EnterTest%0:dGrade,0),0)',[i]); + sParts[6] := sParts[6] + format(' and case coalesce(ep.psycho_mode%0:d,0) '+ + 'when -3 then true '+ + 'when -2 then coalesce(a1.EnterTest%0:dGrade,'''')=''да'' '+ + 'when -1 then coalesce(a1.EnterTest%0:dGrade,'''')=''нет'' '+ + 'else ROUND(10000*coalesce(a1.EnterTest%0:dGrade,0))>=ROUND(10000*coalesce(ep.psycho_pass%0:d,ep.psycho_min%0:d,-10)) '+ + 'end ', + [i]); + sParts[8] := sParts[8] + format( + ', IF( ep.psycho_mode%0:d=-1 AND coalesce(a1.EnterTest%0:dGrade,'''')<>''нет'',ep.psycho_name%0:d,null) ',[i]); + sParts[8] := sParts[8] + format( + ', IF( ep.psycho_mode%0:d=-2 AND coalesce(a1.EnterTest%0:dGrade,'''')<>''да'',concat(''не пройден тест: ('',ep.psycho_name%0:d,'')''),null) ',[i]); + sParts[8] := sParts[8] + format( + ', IF( ep.psycho_mode%0:d in (0,1,2,3,4,5,6,7) '+ + ' AND ROUND(10000*coalesce(a1.EnterTest%0:dGrade,0))= coalesce(g.Pass%0:dGrade,0),false) or NULLIF(a1.EnterExam%0:dName,'''') IS NULL) ',[i]); + sParts[5] := sParts[5] + format( + ' and coalesce(a1.EnterExam%0:dGrade NOT LIKE ''н%%'',true) ',[i]); + + sParts[7] := sParts[7] + format( + ', IF (NULLIF(a1.EnterExam%0:dName,'''') IS NOT NULL '+ + ' AND (coalesce(gradevalue(a1.EnterExam%0:dGrade,1)< coalesce(g.Pass%0:dGrade,0),false) '+ + ' OR coalesce(a1.EnterExam%0:dGrade LIKE ''незачет%%'',false)),a1.EnterExam%0:dName,null) ',[i]); + + end; + end; + + SParts[9] := 'true and (coalesce(gradevalue(a1.EnterExam1Grade,1)>=coalesce(g.Pass1Grade,0),false) '+ + 'or coalesce(gradevalue(a1.EnterExam2Grade,1)>=coalesce(g.Pass2Grade,0),false) '+ + 'or coalesce(gradevalue(a1.EnterExam3Grade,1)>=coalesce(g.Pass3Grade,0),false) '+ + 'or coalesce(gradevalue(a1.EnterExam4Grade,1)>=coalesce(g.Pass4Grade,0),false) '+ + 'or coalesce(gradevalue(a1.EnterExam5Grade,1)>=coalesce(g.Pass5Grade,0),false) '+ + 'or coalesce(gradevalue(a1.EnterExam6Grade,1)>=coalesce(g.Pass6Grade,0),false)) '; + + SQL := format( + 'DROP TABLE IF EXISTS tmp_rpt_applicant_us; '+ + 'CREATE TEMPORARY TABLE tmp_rpt_applicant_us AS '+ + 'SELECT '+ + ' a.xp_key, '+ + ' a.s_year_id, '+ + ' a.trajectory, '+ + ' subj.Subject, '+ + ' CONCAT_WS('' '',UPPER(a.Child_LastName),a.Child_FirstName,a.Child_MidName) AS FIO, '+ + ' a.Child_Birth, '+ + ' a.Child_Class, '+ + ' CASE WHEN a.testready<>0 and coalesce(a.scr_fail,0) = 0 THEN ''годен'' ELSE ''не годен'' END AS isready, '+ + ' COALESCE(a.coeff,1) as coeff, '+ + ' a.sex, '+ + ' COALESCE(NULLIF(a2.Privilege,''''),''нет'') as Privilege , '+ + ' a2.Priv_Count, a2.priv_super, a2.Priv_M > 0 and priv_with_exam and a2.TestPassed and a.testready as priv_m, '+ + ' a.scr_fail, '+ + ' a2.Grade_RUS, '+ + ' a2.Grade_MATH, '+ + ' a2.Grade_INO, '+ + ' a2.Grade_PHYS, '+ + ' CASE WHEN COALESCE(ep.psychomode,0)=0 THEN '+ + ' ROUND(a2.psycho_grade,2) '+ + ' ELSE CASE WHEN a.Psychologist<>0 THEN ''Зачет'' ELSE ''Незачет'' END '+ + ' END as Psycho, '+ + + ' a.applicant_status_id NOT IN (5,8) '+ + ' AND COALESCE(a.scr_fail,0)=0 '+ + ' AND ( a2.ExamPassed AND a2.TestPassed '+ + ' AND (COALESCE(ep.psychomode,0)=0 AND ROUND(10000*a2.Psycho_grade)>=ROUND(10000*coalesce(ep.psycho_pass,0)) OR a.Psychologist<>0 AND ep.psychomode<>0) '+ + ' OR a2.Priv_super<>0 OR (a2.priv_m > 0 and priv_with_exam AND a2.TestPassed and a.testready)) '+ + 'as ExamOK, '+ + ' calc_applicant_ball(a.xp_key) AS Ball, '+ + ' calc_applicant_ball_priv_m(a.xp_key) as Ball_m, '+ + ' CASE '+ + ' WHEN a.absent <> 0 THEN CASE a.Sex WHEN ''женский'' THEN ''не явилась'' ELSE ''не явился'' END '+ + ' WHEN a.scr_fail <> 0 THEN a.screening '+ + ' WHEN a.applicant_status_id = 5 THEN ''Отказано'' '+ + ' WHEN a.applicant_status_id = 8 THEN ''Отказ от обучения'' '+ + ' WHEN a2.priv_super>0 THEN '''' '+ + ' WHEN a2.priv_m > 0 THEN TRIM(CONCAT(IF(COALESCE(ep.psychomode,0)=0 AND ROUND(10000*a2.Psycho_grade)0,''Не рекомендуется к обучению по результатам психологического отбора'', ''''), ' + + ' CASE WHEN LOCATE(''физическая культура'', a2.FailedExams) > 0 THEN '' Не сданы: физическая культура'' ELSE '''' END)) '+ + ' WHEN nd.errors<>'''' THEN nd.errors '+ + ' WHEN NOT a2.TestPassed or NOT a2.ExamPassed '+ + ' or COALESCE(ep.psychomode,0)=0 AND ROUND(10000*a2.Psycho_grade)0 '+ + ' THEN CONCAT_WS('',\n '','+ + ' IF (COALESCE(ep.psychomode,0)=0 AND ROUND(10000*a2.Psycho_grade)0,''Не рекомендуется к обучению по результатам психологического отбора'',NULL), '+ + ' IF(NOT a2.TestPassed AND a2.Priv_super=0, a2.FailedTests,NULL), '+ + ' IF(NOT a2.ExamPassed AND a2.Priv_super=0, CONCAT(''Не сданы: \n'',a2.FailedExams),NULL)'+ + ' ) '+ + ' END AS Prim, '+ + ' a2.FailedExams, a2.FailedTests, a2.ExamPassed,a2.TestPassed, '+ + ' CASE WHEN a.absent<>0 THEN 1 ELSE 0 END as absent, '+ + ' a2.col1,a2.col2,a2.col3,a2.col4,a2.col5,a2.col6, '+ + ' a2.grade1,a2.grade2,a2.grade3,a2.grade4,a2.grade5,a2.grade6 '+ + 'FROM ( '+ + ' SELECT a1.xp_key, '+ + ' GROUP_CONCAT(distinct CASE '+ + ' WHEN np.code = ''Л'' THEN ''Указ Президента РФ от 09.05.2022 г. №268 п.3/ч.1.'' '+ + ' WHEN np.code = ''Л1'' THEN ''Указ Президента РФ от 09.05.2022 г. №268 п.3/ч.2.'' '+ + ' WHEN np.code = ''М'' THEN ''Федеральный закон от 29 декабря 2022 года «641-ФЗ, статья 86, часть 6.1'' '+ + ' WHEN np.code IS NOT NULL THEN priv.PrivilegeCode '+ + ' ELSE priv.PrivilegeName END order by priv.PrivilegeCode SEPARATOR '', '') AS Privilege, '+ + ' COUNT(priv.PrivilegeCode) AS Priv_Count, '+ + ' SUM(if(np.code in ( ''Л''),1, 0) ) AS Priv_super, '+ + ' SUM(if(np.code in (''М''),1,0)) as Priv_M, '+ + ' get_applicant_grade(a1.xp_key,''%%русский%%'','''') AS Grade_RUS, '+ + ' get_applicant_grade(a1.xp_key,''%%математика%%'','''') AS Grade_MATH, '+ + ' get_applicant_grade(a1.xp_key,''%%язык%%'',''%%русский%%'') AS Grade_INO, '+ + ' get_applicant_grade(a1.xp_key,''%%физ%%'',''физика'') AS Grade_PHYS, '+ + ' %3:s '+ + ' %4:s '+ + ' (0.0 %2:s )/COALESCE(ep.psycho_denom,5) as Psycho_grade, '+ + ' (TRUE %5:s %6:s) as ExamPassed, '+ + ' (TRUE %7:s ) as TestPassed, '+ + ' %12:s as priv_with_exam, '+ + + ' CONCAT_WS('', '' %8:s ) as FailedExams, '+ + ' CONCAT_WS('', '' %9:s ) as FailedTests '+ + ' FROM xp_applicant a1 '+ + ' LEFT JOIN xp_applicant_file_privilege priv ON priv.mid=a1.xp_key AND priv.PrivilegeCode <> ''-'' '+ + ' LEFT JOIN c_privilege np ON np.code=priv.PrivilegeCode '+ + ' JOIN applicant_group g ON g.id=a1.applicant_group '+ + ' LEFT JOIN enroll_params ep ON ep.school_year=a1.s_year_id '+ + ' WHERE a1.s_year_id=%0:d AND a1.Child_Class>0 AND a1.testready <> 0 AND applicant_status_id <> 4 '+ + ' AND (%1:d=COALESCE(a1.stream,0)) '+ + DateFilter + + ' GROUP BY a1.xp_key '+ + ') a2 '+ + ' JOIN xp_applicant a ON a.xp_key = a2.xp_key '+ + ' LEFT JOIN enroll_params ep ON ep.school_year=a.s_year_id '+ + ' LEFT JOIN tmp_rpt_problems nd ON nd.xp_key=a.xp_key '+ + ' LEFT JOIN xp_subjects subj ON subj.Subject = a.Subject; ', + [idyear,cbStream, + sParts[1], + sParts[2], + sParts[3], + sParts[4], + sParts[5], + sParts[6], + sParts[7], + sParts[8], + TNIDBDM.StringAsSQL(Arguments.Keys.Values['fromdate']), + TNIDBDM.StringAsSQL(Arguments.Keys.Values['todate']), + sParts[9]]); +// FS:= TstringList.create; +// fs.Add(sql); +// fs.SaveToFile('sqlappldebug.sql'); + //xpInformation(sql); + connect.processor.ExecuteSQL(SQL); + + colSorter := MakeCols(); + SQL := format( + 'DROP TABLE IF EXISTS tmp_rpt_applicant; '+ + 'CREATE TEMPORARY TABLE tmp_rpt_applicant AS '+ + ' SELECT a1.*, e.places, e.places_male, e.places_female, '+ + ColSorter+ + 'CASE '+ + ' WHEN COALESCE(a1.absent,0)=0 THEN ' + + ' CASE ' + + ' WHEN a1.undefined<>'''' THEN 5 '+ + ' WHEN NOT ExamOK AND priv_super=0 THEN 3 '+ + ' WHEN (a1.row<=a1.place_limit) THEN 0 '+ + + ' ELSE 3 ' + + ' END ' + + ' ELSE 4 ' + + 'END as GroupID ' + + ' FROM ( '+ + ' SELECT t.*, '+ + ' CASE '+ + ' WHEN @kurs=t.Child_Class AND @gender=t.gender AND @track=t.track THEN @row := @row+1 '+ + ' ELSE @row := 1 '+ + ' END as row, '+ + ' @kurs := t.Child_Class, @gender := t.gender, @track := t.track '+ + ' FROM '+ + ' (SELECT @row:=-1 as row, @kurs:=-1 as i_kurs, @gender:=-1 as i_gender, @track:=-1 as i_track ) init, '+ + ' (SELECT '+ +' CASE '+ +' WHEN et.places>0 THEN et.places '+ +' WHEN e.places_female>0 AND a.Sex=''женский'' THEN e.places_female '+ +' WHEN e.places_male>0 AND a.Sex=''мужской'' THEN e.places_male '+ +' ELSE e.places '+ +' END as place_limit, '+ + ' CASE '+ + ' WHEN a.sex=''женский'' AND e.places_female>0 THEN 2 '+ + ' WHEN a.Sex=''мужской'' AND e.places_male>0 THEN 1 '+ + ' ELSE 0 '+ + ' END as gender, '+ + ' CASE '+ + ' WHEN et.trajectory>0 THEN a.trajectory '+ + ' ELSE 0 '+ + ' END as track, '+ + ' a.*,nd.errors as undefined '+ + ' from tmp_rpt_applicant_us a '+ + ' join xp_enroll e ON e.school_year=%0:d and e.kurs=a.child_class AND e.trajectory=0 AND e.places>0 '+ + ' and (e.places_female>0 and a.Sex=''женский'' OR e.places_male>0 and a.Sex=''мужской'' OR e.places_female IS NULL AND e.places_male IS NULL) '+ + ' LEFT JOIN xp_enroll et ON et.school_year=%0:d AND et.kurs=a.Child_Class AND et.trajectory=a.trajectory '+ + ' LEFT JOIN tmp_rpt_problems nd ON nd.xp_key=a.xp_key '+ + ' WHERE 1=1 '+ + ' AND (et.trajectory>0 OR NOT EXISTS (SELECT 1 FROM xp_enroll WHERE school_year=a.s_year_id AND kurs=a.Child_Class AND trajectory>0)) '+ + ' ORDER BY a.Child_Class,COALESCE(a.scr_fail,0), absent,IF(nd.errors<>'''',1,0),gender,'+ + ' track,priv_super desc,a.priv_m desc, case when a.priv_m > 0 then a.Ball_m else 0 end desc,ExamOK DESC, ball desc ,case a.Priv_Count when 0 then 1 else 0 end, a.fio '+ + ' ) t '+ + { + ' SELECT IF(Child_Class<>@class OR use_sex AND sex <> @sex,@i:=1,@i:=@i+1) as Row, '+ + ' (@class:=Child_Class) as ClassCopy,(@sex:=sex) as SexCopy, a.* '+ + ' FROM (SELECT u.*,(e.places_male IS NOT NULL OR e.places_female IS NOT NULL) as use_sex,nd.errors as undefined '+ + ' FROM tmp_rpt_applicant_us u '+ + ' LEFT JOIN xp_enroll e ON e.kurs=u.Child_Class AND e.school_year=u.s_year_id '+ + ' LEFT JOIN tmp_rpt_problems nd ON nd.xp_key=u.xp_key '+ + ' ORDER BY Child_Class,absent,ExamOK DESC,IF(nd.errors<>'''',1,0), '+ + ' CASE WHEN e.places_male IS NOT NULL OR e.places_female IS NOT NULL THEN Sex ELSE 0 END, '+ + ' coalesce(Ball,0) DESC, CASE COALESCE(Priv_Count,0) WHEN 0 THEN 0 ELSE 1 END DESC, fio) a, '+ + ' (select @i:=0,@class:=null,@sex:=null) as z '+ } + ' ) a1 '+ + //' LEFT JOIN tmp_rpt_problems nd ON nd.xp_key=a1.xp_key '+ + ' LEFT JOIN xp_enroll e ON e.kurs=a1.Child_Class AND e.school_year=%0:d AND e.trajectory=0; '{+ + ' LEFT JOIN tmpExams e1 ON e1.ExamName = a1.Col1 '+ + ' LEFT JOIN tmpExams e2 ON e2.ExamName = a1.Col2 '+ + ' LEFT JOIN tmpExams e3 ON e3.ExamName = a1.Col3 '+ + ' LEFT JOIN tmpExams e4 ON e4.ExamName = a1.Col4 '+ + ' LEFT JOIN tmpExams e5 ON e5.ExamName = a1.Col5 '+ + ' LEFT JOIN tmpExams e6 ON e6.ExamName = a1.Col6 '}, + [idYear]); + //xpInformation(sql); + connect.processor.ExecuteSQL(SQL); + + SQL := + 'UPDATE xp_applicant a '+ + ' JOIN tmp_rpt_applicant r ON r.xp_key = a.xp_key '+ + 'SET a.passed = null, a.ball = null; '; + connect.processor.ExecuteSQL(SQL); + SQL := + 'UPDATE xp_applicant a '+ + ' JOIN tmp_rpt_applicant r ON r.xp_key = a.xp_key '+ + 'SET a.passed = r.GroupID IN (0,1,2), a.ball = r.ball; '; + connect.processor.ExecuteSQL(SQL); + + result := colcount; +end; + + +class function TRepApplicantResult.CommandSubClass: string; +begin + Result:='applicant_results'; +end; + +procedure TRepApplicantResult.Prepare; +var + SQL: string; +begin + inherited Prepare; + idYear := getInt('year'); + cbStream := getInt('stream'); + UpdateEnrollStatus(); + SQL := format( + 'DROP TABLE IF EXISTS tmp_members; '+ + 'CREATE TEMPORARY TABLE tmp_members AS '+ + ' SELECT xp_f_get_mid_fio(m.mid,0) as member FROM enroll_comitet c '+ + ' JOIN enroll_comitet_members m ON m.enroll_comitet = c.id '+ + 'WHERE c.school_year=%0:d AND coalesce(c.stream,0)=%1:d ORDER BY 1; ' , + [idYear,cbStream]); + connect.processor.ExecuteSQL(SQL); +end; + +procedure TRepApplicantResult.OnFillVariables(AVariables: TxpMemParamManager); +var + i: integer; + ColSorter: string; + SQL: string; + extra_params: array [1..ApplicantExtraParamCnt] of boolean; + Z2: string; + separate_enroll: boolean; + +begin + ColSorter:=''; + for I := 1 to ColCount do + AVariables['Grade'+inttostr(i)] := (ColNames[i]); + for I := ColCount+1 to 12 do + begin + ColSorter := ColSorter+ Format('NULL AS Exam%d, ',[i]); + AVariables['Grade'+inttostr(i)] := (''); + end; + for i := 1 to ApplicantExtraParamCnt do + begin + extra_params[i] := connect.processor.QueryIntValue(format('SELECT coalesce(%s,0) FROM enroll_params WHERE school_year=%d',[ApplicantExtraFields[i], idYear]))=1; + if extra_params[i] then + AVariables['ball_extra_'+inttostr(i)] := 1 + else + AVariables['ball_extra_'+inttostr(i)] := 0; + end; + if (connect.processor.QueryIntValue('SELECT COUNT(*) as cnt FROM tmp_rpt_problems')>0) then + AVariables['rpt_ready'] := 'ПРЕДВАРИТЕЛЬНЫЕ'#13#10'результаты' + else + AVariables['rpt_ready'] := ('Результаты'); + SQL := format( + 'SELECT xp_f_get_mid_fio(c.chairman,0) as chairman , xp_f_get_mid_fio(c.deputy,0) as deputy, xp_f_get_mid_fio(c.deputy2,0) as deputy2, xp_f_get_mid_fio(c.secretary,0) as secretary '+ + 'FROM enroll_comitet c WHERE c.school_year = %0:d AND coalesce(c.stream,0)=%1:d; ', + [idYear,cbStream]); + with connect.Processor.getData(SQL) do + try + if Not eof then + begin + AVariables['Председатель'] := (FieldByName('Chairman').AsString); + AVariables['Заместитель'] := (FieldByName('Deputy').AsString); + Z2 := FieldByName('Deputy2').AsString; + AVariables['Секретарь'] := (FieldByName('Secretary').AsString); + end + else + begin + AVariables['Председатель'] := (''); + AVariables['Заместитель'] := (''); + Z2 := ''; + AVariables['Секретарь'] := (''); + end; + AVariables['Заместитель2'] := (Z2); + if Z2<>'' then AVariables['zam2'] := 1 else AVariables['zam2'] := 0; + finally + Free; + end; + //Variables['Year'] := YearOf(Date); + AVariables['Year'] := connect.processor.QueryValue('SELECT YEAR(begdate) FROM school_year WHERE xp_key = ' + inttostr(idyear)); + +end; +Initialization + TCommandCollection.Register(TRepApplicantResult); + +end. + diff --git a/tcpclient.pas b/tcpclient.pas index 7c4d7c3..47cb239 100644 --- a/tcpclient.pas +++ b/tcpclient.pas @@ -48,7 +48,8 @@ implementation procedure TClientMainThread.SynchAnswer; begin - fOnComplete(self,fmode,fResult.code,fResult.Param,fResult.Name,fResult.Keys,fResult.iValues,fResult.Data); + if assigned(fOnComplete) then + fOnComplete(self,fmode,fResult.code,fResult.Param,fResult.Name,fResult.Keys,fResult.iValues,fResult.Data); end; constructor TClientMainThread.Create(ACommand: string; AFields: TStrings; @@ -73,16 +74,18 @@ begin inherited Destroy; end; + procedure TClientMainThread.execute; begin doStart; log(mtExtra, self,'start main thread'); Connect.Connect(Host,Port); - while not terminated do + while not terminated and not Complete do begin Connect.CallAction; sleep(10); end; + TerminateClients; Connect.Disconnect(); log(mtExtra, self,'terminated'); end; diff --git a/tcpserver.pas b/tcpserver.pas index 031bd25..05f8ce0 100644 --- a/tcpserver.pas +++ b/tcpserver.pas @@ -64,7 +64,7 @@ begin log(mtExtra,self,'start main thread'); Connect.Listen(Port); n := 0; - while not terminated do + while not terminated and not Complete do begin try Connect.CallAction; @@ -81,6 +81,7 @@ begin end; inc(n); end; + TerminateClients; end; constructor TServerMainThread.Create(ALogger: TLogger; APort: integer; diff --git a/tcpthreadhelper.pas b/tcpthreadhelper.pas index 10b716e..8cbe5b3 100644 --- a/tcpthreadhelper.pas +++ b/tcpthreadhelper.pas @@ -86,11 +86,15 @@ type flogger: TLogger; fThreadClass: TConnectionThreadClass; fStarted:boolean; + fComplete: boolean; + function getThread(index: TLSocket): TConnectionThread; - procedure TerminateClients; + protected procedure Log(ALevel: TLogLevel; Sender:TObject; msg: string); procedure doStart; virtual; + procedure TerminateClients; + property Complete: boolean read fComplete; public property Port: integer read fPort; property Connect: TLTCP read fCon; @@ -106,6 +110,8 @@ type procedure NetError(const msg: string; aSocket: TLSocket); constructor Create(AThreadClass: TConnectionThreadClass; ALogger: TLogger; APort: integer); destructor Destroy; override; + procedure SetComplete; + end; @@ -127,11 +133,11 @@ begin if TConnectionThread(fclients[i]).Socket=index then begin result := TConnectionThread(fclients[i]); - log(mtDebug,self,format('getThread(%d) %s',[index.Handle,guidToString(result.ID)])); + log(mtExtra,self,format('getThread(%d) %s',[index.Handle,guidToString(result.ID)])); exit; end; result := fThreadClass.Create(self,index); - log(mtDebug,self,format('new Thread(%d) %s',[index.Handle,guidToString(result.ID)])); + log(mtExtra,self,format('new Thread(%d) %s',[index.Handle,guidToString(result.ID)])); fclients.Add(Result); end; @@ -141,13 +147,13 @@ var i: integer; clt: TConnectionThread; begin - log(mtDebug,Self,'Terminate Clients'); + log(mtExtra,Self,'Terminate Clients'); for i := fclients.Count-1 downto 0 do begin sleep(0); clt := TConnectionThread(fclients[i]); try - log(mtDebug,self,GuidToString(clt.ID)); + log(mtExtra,self,GuidToString(clt.ID)); clt.Terminate; clt.WaitFor; clt.free; @@ -170,6 +176,7 @@ end; procedure TMainThread.doStart; begin fStarted := true; + fComplete:=false; end; procedure TMainThread.RemoveClient(clt: TConnectionThread); @@ -182,7 +189,7 @@ procedure TMainThread.dataReady(aSocket: TLSocket); var clt: TConnectionThread; begin - log(mtDebug,self,'dataReady'); + log(mtExtra,self,'dataReady'); if Terminated then exit; clt := Client[aSocket]; @@ -207,10 +214,10 @@ procedure TMainThread.Accept(aSocket: TLSocket); var clt: TConnectionThread; begin - log(mtDebug,self,'connect'); + log(mtExtra,self,'connect'); if Terminated then exit; clt := Client[aSocket]; - log(mtDebug,self,format('connected %s on %d ',[GUIDToString(clt.ID), aSocket.Handle])); + log(mtExtra,self,format('connected %s on %d ',[GUIDToString(clt.ID), aSocket.Handle])); ProcessAccept(clt); clt.start; @@ -221,11 +228,11 @@ var clt: TConnectionThread; begin if terminated then exit; - log(mtDebug,self,'disconnect'); + log(mtExtra,self,'disconnect'); try clt := Client[aSocket]; if clt.terminated then exit; - log(mtDebug,self,format('disconnected %s on %d ',[GUIDToString(clt.ID), aSocket.Handle])); + log(mtExtra,self,format('disconnected %s on %d ',[GUIDToString(clt.ID), aSocket.Handle])); clt.Terminate; fclients.remove(clt); @@ -241,10 +248,10 @@ procedure TMainThread.doConnect(aSocket: TLSocket); var clt: TConnectionThread; begin - log(mtDebug,self,'doConnect'); + log(mtExtra,self,'doConnect'); if Terminated then exit; clt := Client[aSocket]; - log(mtDebug,self,format('connected %s on %d ',[GUIDToString(clt.ID), aSocket.Handle])); + log(mtExtra,self,format('connected %s on %d ',[GUIDToString(clt.ID), aSocket.Handle])); ProcessConnect(clt); clt.Start; end; @@ -252,8 +259,7 @@ end; procedure TMainThread.TerminatedSet; begin inherited TerminatedSet(); - if fStarted then - TerminateClients; + end; @@ -281,7 +287,7 @@ begin Connect.OnDisconnect:=@doDisconnect; Connect.OnReceive:=@dataReady; Connect.Timeout:=100; - log(mtDebug,self,'create main thread'); + log(mtExtra,self,'create main thread'); end; destructor TMainThread.Destroy; @@ -291,6 +297,11 @@ begin Inherited Destroy; end; +procedure TMainThread.SetComplete; +begin + fComplete:=true; +end; + { TConnectionThread } @@ -592,7 +603,7 @@ var begin log(mtExtra,'Send buffer '+inttostr(len)); try - rem := len+Sizeof(integer)+Sizeof(QWord); + rem := len+Sizeof(dword)+Sizeof(QWord); p := GetMem(rem); try t := p; @@ -631,13 +642,25 @@ var part_id: QWORD; begin result := false; - if Terminated then exit; + if Terminated then + begin + log(mtExtra,'ReceiveBuffer terminated'); + exit; + end; try Cache.Read(part_id); - if Part_id<>PacketStart then exit; + if Part_id<>PacketStart then + begin + log(mtError,'ReceiveBuffer PacketStart '+inttohex(part_id,16)); + exit; + end; Cache.Read(len); - if len=0 then exit; setlength(Buffer,len); + if len=0 then + begin + log(mtError,'ReceiveBuffer Length=0'); + exit; + end; rem := len; p := PByte(Buffer); repeat @@ -740,6 +763,8 @@ procedure TConnectionThread.SendMessage(const mode: byte; const CommandID: DWORD; const QParam: QWord; const AValue: string; const AKeys: TStrings; const IntData: TParamArray; const AData: TStream); begin + + try if assigned(AKeys) and assigned(AData) and (length(IntData)>0) then begin SendHeader(7,mode,CommandID,QParam,AValue); @@ -782,6 +807,13 @@ begin end else SendHeader(0,mode,CommandID,QParam,AValue); + + except on E:exception do + begin + log(mtError,format('send error(%s) %s',[e.classname,e.message])); + raise; + end; + end; end; @@ -796,6 +828,7 @@ var footer: dWORD; begin try + log(mtExtra,'Send Stream '+inttostr(Data.Size)); setLength(Buffer,1+Data.Size+8); pos := 0; AddToBuffer(part,Buffer,pos); @@ -815,6 +848,7 @@ var pos: integer; footer: dWORD; begin + log(mtExtra,'Send Buffer '+inttostr(length(Data))); try setLength(Buffer,length(Data)+4+1); pos := 0; @@ -837,6 +871,7 @@ var len,i: integer; begin try + log(mtExtra,'Send Strings'); LogStrings(mtExtra,fOwner.flogger,self,'KEYS',Data); len := 1+4+8*Data.Count; for i:=0 to Data.Count-1 do @@ -869,6 +904,7 @@ var Buffer: TBuffer; begin len := length(Data); + log(mtExtra,'Send ParamArray '+inttostr(length(Data))); InitBuffer(Sizeof(byte)+(len+1)*SizeOf(DWORD),Buffer,pos); AddToBuffer(part,Buffer,pos); AddToBuffer(len,Buffer,pos); @@ -923,6 +959,9 @@ begin ReadFromBuffer(b,Buffer,pos); if b<>1 then raise EFormatException.Create(''); ReadFromBuffer(Keys,Buffer,pos); + LogStrings(mtExtra,fOwner.flogger,self,'Values',Keys); + if not ReceiveBuffer(Buffer,len) then exit; + pos := 0; ReadFromBuffer(b,Buffer,pos); if b<>2 then raise EFormatException.Create(''); ReadFromBuffer(Data,Buffer,pos); @@ -933,6 +972,8 @@ begin ReadFromBuffer(b,Buffer,pos); if b<>1 then raise EFormatException.Create(''); ReadFromBuffer(intData,Buffer,pos); + if not ReceiveBuffer(Buffer,len) then exit; + pos := 0; ReadFromBuffer(b,Buffer,pos); if b<>2 then raise EFormatException.Create(''); ReadFromBuffer(Data,Buffer,pos); @@ -943,6 +984,8 @@ begin ReadFromBuffer(b,Buffer,pos); if b<>1 then raise EFormatException.Create(''); ReadFromBuffer(Keys,Buffer,pos); + if not ReceiveBuffer(Buffer,len) then exit; + pos := 0; ReadFromBuffer(b,Buffer,pos); if b<>2 then raise EFormatException.Create(''); ReadFromBuffer(intData,Buffer,pos); @@ -953,9 +996,13 @@ begin ReadFromBuffer(b,Buffer,pos); if b<>1 then raise EFormatException.Create(''); ReadFromBuffer(Keys,Buffer,pos); + if not ReceiveBuffer(Buffer,len) then exit; + pos := 0; ReadFromBuffer(b,Buffer,pos); if b<>2 then raise EFormatException.Create(''); ReadFromBuffer(intData,Buffer,pos); + if not ReceiveBuffer(Buffer,len) then exit; + pos := 0; ReadFromBuffer(b,Buffer,pos); if b<>3 then raise EFormatException.Create(''); ReadFromBuffer(Data,Buffer,pos); @@ -977,7 +1024,7 @@ var begin inherited Create(true); //FreeOnTerminate:=true; - fCache := TRoundBuffer.Create(10000); + fCache := TRoundBuffer.Create(@AOwner.log, 1024*1024); fSocket := ASocket; fOwner := AOwner; CreateGuid(ID);