Esther,
Here you go!
I know this is a lot of code, but I've snipped out most of the database updating and other code that is not specifically related to the overall process.
If you'd like to talk on the phone, I'm in East Texas... just send me an email to Scott AT S2Software DOT com
Scott
This first code block is the OnClick for starting the monthly billing process.
- Code: Select all
procedure TframePhoneSystemClientList.buttonRunMonthlyBillingJQButtonOptionsClick(Sender: TObject; AParams: TStringList);
var
iBillingCycleID: Integer;
begin
IWCGMessageDlg('Run Monthly Phone System Billing now?',mtConfirmation,[mbYes,mbCancel],
procedure(Dialog: TIWCGJQMsgDialog; AResult: TModalResult)
begin
if AResult = mrYes then
begin
iBillingCycleID := S2Snax.StringToInteger(Self.comboBillingCycle.SelectedItem.Value);
UserSession.StartMonthlyPhoneSystemBillingProcessThread('Monthly Phone System Billing Process', iBillingCycleID);
Self.timerMonthlyPhoneSystemBilling.Enabled := True;
// this code displays a modal dialog with a cancel button, effectively locking the screen while the thread is processing
Self.FmsgDlgCancel := IWCGMessageDlg('Creating Monthly Phone System Invoices...',mtInformation,[mbCancel],
procedure(Dialog: TIWCGJQMsgDialog; AResult: TModalResult)
begin
if (AResult = mrCancel) then
begin
// loop until the thread is not "SuperBusy"
while UserSession.MonthlyPhoneSystemBillingProcessThread.SuperBusy do Sleep(100);
// if the user clicks the Cancel button AND the thread has not .Finished, then terminate the thread. Keep in mind, the looping mechanisim
// inside the thread is iterating much more quickly than the TIWTimer.OnAsyncTimer and as a result, the thread might have finished between the
// time the user clicks the Cancel button and the next line of code. If this happens, then the thread "wins" and the process is completed, even
// though the user clicked the Cancel button.
if (not UserSession.MonthlyPhoneSystemBillingProcessThread.Finished) then
UserSession.MonthlyPhoneSystemBillingProcessThread.Terminate;
// we MUST make sure that the TIWCGJQMessageDlg is nil when the user closes the dialog, otherwise Assigned(Self.FmsgDlgCancel) will return TRUE
// when, in fact, the dialog was destroyed when it was closed and is no longer functional, BUT is is NOT nil. I do this because we must close
// the dialog in the TIWTimer.OnAsyncTimer event if the user clicks the "Cancel" button in the middle of the processing.
Self.FmsgDlgCancel := nil;
// Force a full post to ensure that the data on the screen is completely refreshed
Self.RefreshData;
RenderRegionAsync(Self.regionMain, False, True, True);
end;
end);
end;
end);
end;
This is the Async OnTimer event:
- Code: Select all
procedure TframePhoneSystemClientList.timerMonthlyPhoneSystemBillingAsyncTimer(Sender: TObject; EventParams: TStringList);
begin
// if the thread is not nil
if Assigned(UserSession.MonthlyPhoneSystemBillingProcessThread) then
begin
// set the progress bar caption
Self.labelProgressBar.Caption :='<table border="0" align="center">'+
'<tr>'+
'<td>' + UserSession.MonthlyPhoneSystemBillingProcessThread.ProgressBarCaption + '</td>'+
'</tr>'+
'<tr>'+
'<td><b>' + S2Snax.IntegerToString(UserSession.MonthlyPhoneSystemBillingProcessThread.PercentComplete) + '% ' +
'(' + IntToStr(UserSession.MonthlyPhoneSystemBillingProcessThread.RecordsProcessed) + ' of ' +
IntToStr(UserSession.MonthlyPhoneSystemBillingProcessThread.RecordCount) + ')' + '</b></td>' +
'</tr>'+
'</table>';
// update the progress bar and render
Self.progressBar.JQProgressBarOptions.Value := UserSession.MonthlyPhoneSystemBillingProcessThread.PercentComplete;
RenderRegionAsync(Self.regionProgressBar, False, True, True);
// if the thread has been cancelled, then display appropriate message and turn the timer off
if UserSession.MonthlyPhoneSystemBillingProcessThread.Cancelled then
begin
if not S2Snax.IsEmptyString(UserSession.MonthlyPhoneSystemBillingProcessThread.ThreadResultMessage) then
IWCGJQShowMessage.ShowNotification(UserSession.MonthlyPhoneSystemBillingProcessThread.ThreadResultMessage, jqsntError, 0);
if UserSession.MonthlyPhoneSystemBillingProcessThread.RecordsProcessed > 0 then
IWCGJQShowMessage.ShowNotification(IntToStr(UserSession.MonthlyPhoneSystemBillingProcessThread.RecordsProcessed) + ' of ' +
IntToStr(UserSession.MonthlyPhoneSystemBillingProcessThread.RecordCount) + ' invoices created and posted successfully.', jqsntLog, 0);
IWCGJQShowMessage.ShowNotification('Monthly Phone System Billing Process Cancelled!', jqsntError, 0);
Self.timerMonthlyPhoneSystemBilling.Enabled := False;
if Assigned(Self.FmsgDlgCancel) then
begin
Self.FmsgDlgCancel.JQDialogOptions.Close;
Self.FmsgDlgCancel := nil;
end;
end
// if the thread has finished successfully, then display appropriate message and turn the timer off
else if (UserSession.MonthlyPhoneSystemBillingProcessThread.Finished) then
begin
if UserSession.MonthlyPhoneSystemBillingProcessThread.RecordsProcessed > 0 then
IWCGJQShowMessage.ShowNotification(IntToStr(UserSession.MonthlyPhoneSystemBillingProcessThread.RecordsProcessed) + ' of ' +
IntToStr(UserSession.MonthlyPhoneSystemBillingProcessThread.RecordCount) + ' invoices created and posted successfully.', jqsntLog, 0);
IWCGJQShowMessage.ShowNotification('Monthly Phone System Billing Process Complete!', jqsntLog, 0);
Self.timerMonthlyPhoneSystemBilling.Enabled := False;
// if the Cancel dialog has not been destroyed, then close it and make it nil. the MessageDlg is destroyed on .Close, but it is NOT set to nil
if Assigned(Self.FmsgDlgCancel) then
begin
Self.FmsgDlgCancel.JQDialogOptions.Close;
Self.FmsgDlgCancel := nil;
end;
end;
end
else
// if the thread nil, then turn off the timer
begin
Self.timerMonthlyPhoneSystemBilling.Enabled := False;
end;
// if the timer is still .Enabled, then the progress bar stays visible
Self.regionProgressBar.Visible := Self.timerMonthlyPhoneSystemBilling.Enabled;
end;
Here is the base worker thread code:
- Code: Select all
type
TWorkerThread = class(TThread)
private
function GetPercentComplete: Integer;
protected
FThreadName: string;
FCancelled: Boolean;
FRecordCount: Integer;
FRecordsProcessed: Integer;
FPercentComplete: Integer;
FProgressBarCaption: String;
FSuperBusy: Boolean;
function GetTerminated: Boolean;
public
property Cancelled: Boolean read FCancelled;
property RecordCount: Integer read FRecordCount;
property RecordsProcessed: Integer read FRecordsProcessed;
property PercentComplete: Integer read GetPercentComplete;
property ProgressBarCaption: String read FProgressBarCaption;
property SuperBusy: Boolean read FSuperBusy;
constructor Create(ThreadName: String); overload; virtual;
end;
implementation
{ TWorkerThread }
constructor TWorkerThread.Create(ThreadName: String);
begin
inherited Create(False);
FThreadName := ThreadName;
FRecordCount := 0;
FRecordsProcessed := 0;
end;
function TWorkerThread.GetPercentComplete: Integer;
begin
GetPercentComplete := 0;
if Self.FRecordCount > 0 then
GetPercentComplete := Trunc((Self.FRecordsProcessed / Self.FRecordCount)*100);
end;
function TWorkerThread.GetTerminated: Boolean;
begin
GetTerminated := Self.Terminated;
end;
The Monthly Processing thread code:
- Code: Select all
type
TMonthlyPhoneSystemBillingProcessThread = class(TWorkerThread)
private
FUserSession: TObject;
FADOConnection: TADOConnection;
FSQLConnectionString: String;
FBillingCycleID: Integer;
FThreadResult: TFunctionResult;
FThreadResultMessage: String;
procedure CalculateLDCharges(
ClientName: String;
EntityID: Integer;
ClientLDOptionsID: Integer;
OrderID: Integer;
PostingDate: TDateTime;
DetailLineTypeID: Integer;
DetailLineType: String;
IsTaxable: Boolean;
IsGST: Boolean;
GLAccountDRID: Integer;
GLAccountCRID: Integer);
protected
procedure Execute; override;
public
property ThreadResult: TFunctionResult read FThreadResult;
property ThreadResultMessage: String read FThreadResultMessage;
constructor Create(UserSession: TObject; ThreadName: String; SQLConnectionString: String; BillingCycleID: Integer); overload; virtual;
end;
And, finally, the monthly billing thread code.
- Code: Select all
procedure TIWUserSession.StartMonthlyPhoneSystemBillingProcessThread(ThreadName: String; BillingCycleID: Integer);
begin
Self.FMonthlyPhoneSystemBillingProcessThread := TMonthlyPhoneSystemBillingProcessThread.Create(Self, Name, Self.SQLConnectionString, BillingCycleID);
end;
{ TMonthlyPhoneSystemBillingProcessThread }
constructor TMonthlyPhoneSystemBillingProcessThread.Create(UserSession: TObject; ThreadName, SQLConnectionString: String; BillingCycleID: Integer);
begin
inherited Create(ThreadName);
Self.FUserSession := (UserSession as TIWUserSession);
Self.FSQLConnectionString := SQLConnectionString;
Self.FBillingCycleID := BillingCycleID;
Self.FSuperBusy := False;
end;
procedure TMonthlyPhoneSystemBillingProcessThread.Execute;
var
adoPhoneSystemItems, adoClients, adoOrder, adoOrderDetail, adoTenant: TADODataSet;
strSQL, strOrderNumber, strClientName, strPhoneSystemName, strPeriod: String;
iOrderID, iEntityID, iCompanyLocationID, iClientLDOptionsID: Integer;
dGLJournalBatchID: Double;
datePosted: TDateTime;
resultPosting: TFunctionResult;
resultPhoneSystem: TFunctionResult;
begin
Self.FADOConnection := TADOConnection.Create(nil);
Self.FADOConnection.ConnectionString := Self.FSQLConnectionString;
Self.FADOConnection.Open;
adoPhoneSystemItems := TADODataSet.Create(nil);
adoPhoneSystemItems.Connection := Self.FADOConnection;
adoPhoneSystemItems.CursorType := TCursorType.ctOpenForwardOnly;
<< SNIP >>
// Thread properties
Self.FRecordCount := adoClients.RecordCount;
Self.FRecordsProcessed := 0;
// this should be set to the "selected" company_location_id if there are multiple locations.
iCompanyLocationID := 1;
while (not adoClients.Eof) and (not Self.FCancelled) do
begin
dGLJournalBatchID := (Self.FUserSession as TIWUserSession).GL_CreateGLJournalBatchID;
iEntityID := adoClients.FieldByName('entity_id').AsInteger;
// connect to the phone system.
strClientName := adoClients.FieldByName('mail_name').AsString;
strPhoneSystemName := adoClients.FieldByName('phone_system_name').AsString;
datePosted := adoClients.FieldByName('next_billing_date').AsDateTime;
strPeriod := FormatDateTime('YYYYMM', datePosted);
Self.FProgressBarCaption := 'Connecting to ' + strPhoneSystemName + ' Phone System';
Self.FCancelled := Self.Terminated;
if not Self.FCancelled then
begin
Self.FSuperBusy := True;
try
resultPhoneSystem := (Self.FUserSession AS TIWUserSession).ConnectToPhoneSystem(Self.FADOConnection, iEntityID);
if resultPhoneSystem <> TFunctionResult.resultSuccess then
begin
Self.FThreadResult := resultPhoneSystem;
Self.FThreadResultMessage := 'ERROR: ' + CAT5Include.C_FUNCTIONRESULTDESCRIPTION[Ord(resultPhoneSystem)];
Self.Terminate;
end;
finally
end;
Self.FSuperBusy := False;
end;
Self.FCancelled := Self.Terminated;
if not Self.FCancelled then
begin
Self.FProgressBarCaption := 'Retrieving ' + strPhoneSystemName + ' data for ' + strClientName;
Self.FSuperBusy := True;
adoPhoneSystemItems := (Self.FUserSession AS TIWUserSession).GetClientMonthlyPhoneSystemBillingCharges(Self.FADOConnection, iEntityID);
if not Assigned(adoPhoneSystemItems) then
begin
Self.FThreadResult := TFunctionResult.resultPhoneSystemErrorConnectingToPhoneSystem;
Self.FThreadResultMessage := 'ERROR: ' + CAT5Include.C_FUNCTIONRESULTDESCRIPTION[Ord(Self.FThreadResult)];
Self.Terminate;
end;
Self.FSuperBusy := False;
end;
Self.FCancelled := Self.Terminated;
if (not Self.FCancelled) then
begin
// start the SQL transaction
Self.FADOConnection.BeginTrans;
// Create the monthly invoice order record for this hosted phone system customer
strSQL := 'EXEC ' + CAT5Include.C_SP_CREATEORDER + ' ' +
IntToStr(iCompanyLocationID) + ', ' +
'''H'',''' +
FormatDateTime('MM/DD/YYYY', datePosted) + ''',' +
IntToStr(iEntityID);
adoOrder.CommandText := strSQL;
adoOrder.Open;
iOrderID := adoOrder.FieldByName('id').AsInteger;
strOrderNumber := adoOrder.FieldByName('order_number').AsString;
adoOrder.Close;
Self.FProgressBarCaption := 'Creating invoice ' + strOrderNumber + ' dated ' + FormatDateTime('MM/DD/YYYY', datePosted) + ' for ' + strClientName;
<< SNIP >>
// Post the Monthly Invoice to A/R
Self.FThreadResult := (Self.FUserSession as TIWUserSession).PostOrderToAR(iOrderID, datePosted, dGLJournalBatchID, Self.FADOConnection);
if Self.FThreadResult <> TFunctionResult.resultSuccess then
begin
Self.FThreadResultMessage := 'ERROR: ' + CAT5Include.C_FUNCTIONRESULTDESCRIPTION[Ord(Self.FThreadResult)] + ' while attempting to post Order # ' + strOrderNumber + ' for ' + strClientName + ' to the A/R module.';
end
else
begin
// Post the G/L Transactions
Self.FThreadResult := (Self.FUserSession as TIWUserSession).GL_PostGLJournalEntries(Self.FADOConnection, datePosted, dGLJournalBatchID, True);
if Self.FThreadResult <> TFunctionResult.resultSuccess then
begin
Self.FThreadResultMessage := 'ERROR: ' + CAT5Include.C_FUNCTIONRESULTDESCRIPTION[Ord(Self.FThreadResult)] + ' while attempting to post Order # ' + strOrderNumber + ' for ' + strClientName + ' to the G/L module.';
end
end;
if Self.FThreadResult <> TFunctionResult.resultSuccess then
begin
Self.Terminate;
end;
end;
Self.FCancelled := Self.Terminated;
// Commit or Rollback the SQL transaction
if Self.FCancelled then
Self.FADOConnection.RollbackTrans
else
begin
Self.FADOConnection.CommitTrans;
Inc(Self.FRecordsProcessed);
end;
end;
adoClients.Next;
end;
adoClients.Close;
Self.FADOConnection.Close;
end;
// Calculate the Long Distance Charges for this period
procedure TMonthlyPhoneSystemBillingProcessThread.CalculateLDCharges(ClientName: String;
EntityID: Integer;
ClientLDOptionsID: Integer;
OrderID: Integer;
PostingDate: TDateTime;
DetailLineTypeID: Integer;
DetailLineType: String;
IsTaxable: Boolean;
IsGST: Boolean;
GLAccountDRID: Integer;
GLAccountCRID: Integer);
begin
<< SNIP >>
end;