Help with improving/optimising this procedure

diberlee

Registered User.
Local time
Today, 14:00
Joined
May 13, 2013
Messages
85
Hi,


I've made a few posts here and got some help to get me this far, I'm looking for a couple of pointers. A quick rundown of what I'm doing:
  • I have a table with a list of clients to contact in a 2 hour window.
  • Some clients are marked as a priority and should be called first but could appear anywhere on the list.
  • Others have requested a call at a certain time and should be ignored until that time.
  • Once these priority clients are dealt with all others will be worked through in sequence.
  • If contact is unsuccessful the record will be update to show that a contact attempt has been made and a time to try again (10-15 mins most likely) will be entered. These records should be ignored until that time has passed, but then tried again.
  • I have an unbound form where a user clicks "Next" to have VBA code run and select the appropriate record to display. User selects a result for the call and record is saved using another button.
  • Once finished this will be used by 15-25 people at a time.
Here's what I have so far:

Code:
Option Compare Database
Public foundNum As String
Public noShowCutOff As String
 
[COLOR=green]Private Sub bttnNext_Click()[/COLOR] 'user clicks NEXT - select next client to call
Dim cControl As Control
Dim db As Database
Dim rst As Recordset2
Set db = DBEngine(0)(0)
Set rst = db.OpenRecordset("Clients", dbOpenSnapshot)
'Check if user has not saved a reserved record
If Me.txtSaved = False Then
    MsgBox ("Save changes before getting next")
    Exit Sub
End If
 
'find the next client based on priorities
nextFound = False
NextClient
 
'If no more then clear controls to avoid confusion and show message
'otherwise populate boxes with client details
If foundNum = "" Then
    For Each cControl In Me.Controls
        If cControl.Name Like "txt*" Then cControl = vbNullString
    Next
    MsgBox ("No more clients ready to call.")
Else
    strCriteria = "ClientRef = '" & foundNum & "'"
    rst.FindFirst (strCriteria)
    If Not rst.NoMatch Then
        Me.txtName = rst!ClientName
        Me.txtRef = rst!ClientRef
        Me.txtTel = rst!Telephone
        Me.txtType = rst!ReviewType
        Me.txtAttempts = rst!Attempts
        Me.txtTryAgain = rst!TryAgain
        Me.chkLetter = rst!LetterBooking
        Me.txtSaved = "False"
        Debug.Print "Sent details for " & foundNum & " to form."
    Else
 
        Debug.Print "ERROR when getting next (NoMatch = True)"
    End If
End If
rst.Close
Set rst = Nothing
db.Close
Set db = Nothing
End Sub
 
[COLOR=green]Private Sub bttnSave_Click()[/COLOR]
Dim db As Database
Dim rst As Recordset2
Set db = DBEngine(0)(0)
Set rst = db.OpenRecordset("Clients", dbOpenDynaset)
noShowCutOff = "13:30" 'Can't mark as no show until after this time
strCriteria = "ClientRef = '" & Me.txtRef & "'"
rst.FindFirst (strCriteria)
If Not rst.NoMatch Then
    rst.Edit
    rst!Attempts = rst!Attempts + 1
 
    rst!Working = False
    rst.Update
    Me.txtSaved = ""
    Debug.Print "Saved " & rst!ClientName & " " & rst!ClientRef
Else
    Debug.Print "Searched " & strCriteria & ". rst.NoMatch = True"
    MsgBox ("Encountered an error when trying to save result.")
    Exit Sub
End If
rst.Close
Set rst = Nothing
db.Close
Set db = Nothing
End Sub
 
[COLOR=green]Sub NextClient()[/COLOR]
foundNum = findPriority("Priority = True AND Attempts = 0 AND Working = False")
 
If foundNum = "" Then
    Debug.Print "No priority client found - checking for timed"
Else
    Exit Sub
End If
foundNum = findPriority("TimeToCall -2 <= Time() AND Attempts = 0 AND Working = False")
If foundNum = "" Then
    Debug.Print "No timed client found - checking for priority try again"
Else
    Exit Sub
End If
End Sub
 
'receive string to use in FindFirst and return client number if found
[COLOR=green]Public Function findPriority(strCriteria As String) As String[/COLOR]
    Dim db As Database
    Dim rst As Recordset2
    Set db = DBEngine(0)(0)
    Set rst = db.OpenRecordset("Clients", dbOpenDynaset)
 
    rst.FindFirst (strCriteria)
 
    If Not rst.NoMatch Then
        rst.Edit
        rst!Working = True
        rst.Update
        findPriority = rst!ClientRef
        Debug.Print "Reserved " & rst!ClientName & " " & rst!ClientRef
    Else
        findPriority = ""
        Debug.Print "Searched " & strCriteria & ". rst.NoMatch = True"
        Exit Function
    End If
 
'this section will be expanded to check for different criteria as decribed above.
 
    rst.Close
    Set rst = Nothing
    db.Close
    Set db = Nothing
 
End Function

I've got a few questions if anyone would be so kind...

1/ Is there a better way of checking if the user has saved their record? I'm currently writing to a hidden control and checking the value later. Seems a bit sloppy...

2/ When I identify the next record to be worked on I set a flag (Working) to True to prevent 2 users getting the same record. Is this necessary? If yes is there a better way to do it?

3/ When I start building the code for repeat attempts I will need to check the records for previous contact attempts. What's the easiest way to check for the highest value in a certain field? I plan to store it in a public variable and use it in a FindFirst statement, but welcome any suggestions for a better approach

4/ I know it's good practice to close all databases etc and set to nothing. Do I need to do this in every sub and function, or just the final one before code finishes running?

5/ In Excel you can detect if a user closes the form using the X button. I'm using a pop-up form for this and wondered if there is a similar way to capture the close and run my code for exiting gracefully?

Cheers
Diberlee
 
Last edited:
If I was writing a system to do the job you describe I would have another table called something like ContactEvent so that for any one client I might have many contact events.
tClient
ClientID (PK)
ClientName
RefNum
Phone

tContactEvent
ContactEventID (PK)
ClientID (FK)
AgentID (FK) <- your employee
DateTime
Suceeded (yes/no)
Notes

With this design you never edit records, you only create them, and that saves you a lot of the headaches you describe, like concurrent edits, saving (or not) the current record (yes you always save everything). You can bind a recordset to a form in DataEntry mode <- easy.
To count failed contact attempts for a client you do . . .
Code:
SELECT Count(*) 
FROM tContactAttempt 
WHERE ClientID = 123
AND NOT Succeeded
. . . and typically you'd apply date/time constraints to that too, so failed contact attempts since yesterday at 9.

An interesting observation is that you may think that more tables is more work. Notice how in your case much of your effort is required BECAUSE you don't have another table, so adding another table will SAVE you work.

And a final argument: if you have a tContactEvent table, you can save objects in the future!!! So that table can function as a schedule for future contacts that you need to make.
hth
 
Hi lagbolt,

I was actually toying with the idea of having a second table to write results to as that will simplify some of the other bits I need this to do (like importing a new list of clients each day and collating results). I was thinking it would be a bit more work like you said, but I see the sense in putting some more work in up front to make it easier further down the line.

I'll be doing some work on this today and will try and integrate a system like you suggested. I can definitely see how it will make things easier, but I suspect I'll have to hit the books again :)

Cheers
 
Hi again,

I'm looking at getting something like you suggested running. How would I know when a client has been dealt with so that their details are not given to another user?

I'm guessing I would have the code find the next client, and then check through the contacted events for a successful contact? Would it be better to set a completed flag in the main clients table?
 
One check you could do is see if the last contact event for a client succeeded. So if you open a recordset of contact events for a single client and sort descending by DateTime, then the first record will be the last event. Then check the value of the Succeeded field. SQL for that would be something like this . . .
Code:
SELECT Succeeded
FROM tContactEvent
WHERE ClientID = 123
ORDER BY DateTime Desc
I would avoid setting a completed flag in the parent record where that flag is the result of a calculation you do on child records. Instead, just do that calculation every time you need that info. You always want to store all data in its raw form, and only ever process it at retrieval time.
 
I can definitely see the merit in approaching this the way you describe. I was reluctant to start over at first, but after thinking this through yours is clearly a far more elegant approach to the problem. I see now why mine felt so sloppy/clunky. I'll be working on this today and will hopefully get everything running smooth quite quickly. Thanks for your help, I'll keep in mind what you said about not storing redundant information for any future projects using Access. Makes more sense the way you put it than it did in the book I'm reading.

One more question if you don't mind. Is it likely that 2 users would get the same entry? Say they both click next within a fraction of a second; would Access complete the checks for the first to click before responding to the second? Save conflicts are the biggest reason I refused to do this in Excel (once bitten, twice shy) as originally suggested, and decided to look into Access.
 
Yeah, I guess you still need to flag an item as being in progress, I'm not sure, I don't exactly remember all the details of this problem. But one client might be calculated as the next priority, and that record might get loaded by one user for action, and that action still hasn't been taken. Then a second user might query for the next priority, and it should overlook the one the previous user loaded and has yet to act on. Is that the case you mean?
 
Yes, that's the sort of thing 'm concerned about.

I'm using the general "note" field to get around that. I write details on the action to that field, and will check for "reserved" on the most recent entry in the log when considering which client is chosen next. I'm hoping that even if the users click within a fraction of a second of each other the entry in tContactEvents will have been made in time to prevent both users getting the same client.

In any case the worst thing that can happen is that the second user sees that someone is already dealing with that client (via internal office applications) and can select an option saying as much. Since the form only writes to tContactEvents when a result is chosen I don't think it will be an issue for this project.
 
It depends on your approach too. Are you using future events, so do you add a record in tContactEvent to indicate an event should be executed in the future? If so, then maybe have a Succeeded (yes/no) field and an InProgress (yes/no) field, and then as part of your calculation of priorities you don't select anything that is InProgress. This completely solves that problem.

Beyond that, there is no chance that two users will click for the next ContactEvent, calculate the same priority, and load the same record.

Cheers,
 
No, I don't have future events in there but that's something I'll try to add in. My code to select based on the different priorities has to use the dateTime column to check when the last contact attempt was to make sure we leave enough time between tries, so it should be easy enough to add.

You may be able to help with another aspect of this if you don't mind... The data for these appointments comes from a report we can run. We have the option to import to CSV, but there is a gap of 5 rows between the headings and a 2 redundant columns of data. So as far as I know it can't be imported to Access without being manipulated first.

I made a small Excel sheet where the CSV can be imported and a macro takes care of trimming out the bits we don;t need and saving the sheet ready for import in the same directory as the database. I think I can then just write some code to import the new data via an admin form I already created.

I just wondered if there is a way to trim the data from the CSV programmatically within Access so we don't need to use the Excel sheet?
 
Parsing in data from other sources is always tricky, and I can't say from your description what is the best way. I lose you at the first sentence when you say, "The data for these appointments comes from a report we can run." So you run a report? From where? In what format? To file? And there's an import option? So you read a csv file using VBA code???

If you have a file that needs parsing and you post the file I can tell you how I would do it.
 
Hi,

Sorry I should have been clearer. The report I mention is available through an internal reporting tool (not Access) and is set up to just pull the details of appointments we have booked for a certain day using another internal application - we have no control over it other than choosing what format it is saved in (CSV, XML, PDF, MHTML, Excel, TIFF, Word). I choose CSV as saving to Excel causes it to add a lot of merged cells which I'm sure you know can cause problems. Perhaps one of the other options is better?

The way I have set everything up thus far is:

1/ We run the report to get the appointment list and export as CSV.
2/ We paste the list into an Excel sheet (import.xlsm) I created in the same folder as the database.
3/ The sheet has a button that runs VBA code to trim the surplus rows/columns leaving just the 4 that we need. It also adjusts some of the headings to match field names in Access. The sheet saves automatically once that is done.
4/ I am able to import the data from import.xlsm using the menus in Access.

I have attached a copy of the report minus the entries. Row 10 shows what I am currently doing with that heading to prepare the list for import - either changing the text to match the field name in Access or removing that column all together. I also delete rows 1-5 as they contain nothing useful and cause the import to fail - as yet I haven't found a way to look for headings on row 6 when importing. So how would you go about importing this file?

Cheers
Duane
 

Attachments

First I'd see if the csv or the xml options give me a cleaner file. Say you export to xml and use Access tools to import the data. How does that fail? Can you create an import spec that you can run from a button click? I'd check that route first.
 
First I'd see if the csv or the xml options give me a cleaner file. Say you export to xml and use Access tools to import the data. How does that fail? Can you create an import spec that you can run from a button click? I'd check that route first.

Hi,

I wasn't in the office yesterday but just gave this a try. The XML import doesn't actually fail, but it doesn't do anything useful either. It created 2 new tables named "Report" and "Detail_Collection" but created no records in either. I selected the option "Append to existing table" but wasn't given any choices about where to import the data.

I've never tried to create an import spec, but that is what I would like to do. Think I need to read a few more chapters of my book.

I want to create a dashboard where a regular user can just get into the form, but an admin user can enter a password to get access to the tables and buttons to import new reports etc. I think I can do most of that, it's just the import bit that's a slight pain in the butt at the moment as we're having to take extra steps to parse the data. No big deal, but in an ideal world it would just be click a button, select a file and be done :)
 

Users who are viewing this thread

Back
Top Bottom