Update multiple filtered invoices as "paid" and add payment notes without opening each invoice individually

AHMEDRASHED

Member
Local time
Today, 18:36
Joined
Feb 2, 2020
Messages
59
Hello everyone,
I need help with an issue . I want to update multiple filtered invoices as "paid" and add payment notes without opening each invoice individually. Currently, I have a search form (view only) and an invoice form with main forms and subforms. (payment notes in subform)
Is there a VBA code or method to perform this bulk update efficiently?
Your suggestions are greatly appreciated.
Thanks,

Video attached




1721725268028.png
 

Attachments

you can use the recordset of the subform to do the Update:

Code:
With Me.PaymentSubFormNameHere.Form.RecordSetClone
    Do Until .Eof
        .Edit
        !PaidField = -1
        .Update
        .MoveNext
    Loop
End With
 
Will the notes for each paid invoice be identical? If so, you can modify Arnel's code to include that second field in order to update the Payment Notes.

That said, my reading of the question was that the PaidField is in the table bound to the main form.

Please clarify.
 
Code:
Dim sSQL As String
sSQL = "UPDATE YourTable SET PaidField = True, PaymentNote = 'hello'" & _
      " WHERE " & Me.subFormNeme.Form.Filter
CurrentDb.Execute sSQL, dbFailOnError

Is there a VBA code or method to perform this bulk update efficiently?
The first question that comes to mind is always a query.
 
Last edited:
The first question that comes to mind is always a query.
not always.
if you have dirtied the subform and you execute the query, ms access will complain about update conflict.
 
Thank you so much

I added a new button and used the code below. I'm not sure if it's the best approach, but it seems to work fine. I used this code because sometimes I need to make multiple invoices, for example, 50 invoices, and add a payment note, such as "Paid in cash on 24/07/2024," for all of them.

1721807306888.png


Code:
Private Sub btnFillPayNote_Click()
    Dim userNote As String

    ' Prompt the user to enter a note
    userNote = InputBox("Enter the note for PayNote:", "Add PayNote")

    ' Check if user entered a note or cancelled the input
    If userNote <> "" Then
        On Error GoTo ErrHandler
        
        Do While Not Me.Recordset.EOF
            ' Open the selected invoice
            DoCmd.OpenForm "frmInvoice", , , "InvoiceNumber='" & Me.InvoiceNumber & "'"
            DoCmd.Maximize

            ' Fill the "PayNote" in the subform "frmNotes"
            Forms!frmInvoice!frmNotes.Form!PayNote = userNote
            
            ' Set Check94 (isPaid) to True
            Forms!frmInvoice!Check94 = True

            ' Save and close the "frmInvoice" form
            DoCmd.Close acForm, "frmInvoice", acSaveYes

            ' Move to the next record
            DoCmd.GoToRecord , , acNext
        Loop
        
        Exit Sub

ErrHandler:
        ' Check if the error is due to reaching the end of the recordset
        If Err.Number = 2105 Then
            ' Reached the end of the recordset, exit the loop
            MsgBox "All selected invoices have been updated.", vbInformation, "Process Complete"
        Else
            ' Handle other potential errors
            MsgBox "An error occurred: " & Err.Description, vbExclamation, "Error"
        End If
    Else
        MsgBox "No note was entered. The PayNote was not updated.", vbExclamation, "No Note Entered"
    End If
End Sub
 
Do yourself a favour.
Start giving your controls meaaningfule names and not leaving them as check94 and then having to add comments to know what on earth check94 is meant to be. Call it chkPaid or something blnPaid, something along those lines.

I would be using the filtered recordsetclone as @arnelgp proposed, not going to next record until I error.
 
Do yourself a favour.
Start giving your controls meaaningfule names and not leaving them as check94 and then having to add comments to know what on earth check94 is meant to be. Call it chkPaid or something blnPaid, something along those lines.

I would be using the filtered recordsetclone as @arnelgp proposed, not going to next record until I error.

I changed it to "chkPaid". @arnelgprealy, I tried working on it for about 3 hours yesterday but couldn't figure it out. You guys are so professional; when I come here I feel I'm baby in high school . still learning and have a lot to catch up on. Thank you for your comment and your help!
 
You guys are so professional
Not me, I am just a dabbler, and my memory is so bad these days, that I need to do what I just advised you, so if I go back to something, even a few days later, I can understand it. I still add comments, but my variables/controls will have names that indicate what they are for.

I have learnt a lot here since I joined, everyone else really needs to do the same in my oprinion.
Some people just want everything handed to them on a plate. They learn nothing normally. :(
 
Not me, I am just a dabbler, and my memory is so bad these days, that I need to do what I just advised you, so if I go back to something, even a few days later, I can understand it. I still add comments, but my variables/controls will have names that indicate what they are for.

I have learnt a lot here since I joined, everyone else really needs to do the same in my oprinion.
Some people just want everything handed to them on a plate. They learn nothing normally. :(
Thanks for your humble words and sharing your experience, @Gasman . Your advice on using meaningful names and comments for variables and controls is greatly appreciated. I agree that learning never stops, The knowledge and insights shared by you and other members have helped me . I'm grateful for your input, and I look forward to learning more from everyone here.
 
Thank you so much

I added a new button and used the code below. I'm not sure if it's the best approach, but it seems to work fine. I used this code because sometimes I need to make multiple invoices, for example, 50 invoices, and add a payment note, such as "Paid in cash on 24/07/2024," for all of them.

View attachment 115309

Code:
Private Sub btnFillPayNote_Click()
    Dim userNote As String

    ' Prompt the user to enter a note
    userNote = InputBox("Enter the note for PayNote:", "Add PayNote")

    ' Check if user entered a note or cancelled the input
    If userNote <> "" Then
        On Error GoTo ErrHandler
    
        Do While Not Me.Recordset.EOF
            ' Open the selected invoice
            DoCmd.OpenForm "frmInvoice", , , "InvoiceNumber='" & Me.InvoiceNumber & "'"
            DoCmd.Maximize

            ' Fill the "PayNote" in the subform "frmNotes"
            Forms!frmInvoice!frmNotes.Form!PayNote = userNote
        
            ' Set Check94 (isPaid) to True
            Forms!frmInvoice!Check94 = True

            ' Save and close the "frmInvoice" form
            DoCmd.Close acForm, "frmInvoice", acSaveYes

            ' Move to the next record
            DoCmd.GoToRecord , , acNext
        Loop
    
        Exit Sub

ErrHandler:
        ' Check if the error is due to reaching the end of the recordset
        If Err.Number = 2105 Then
            ' Reached the end of the recordset, exit the loop
            MsgBox "All selected invoices have been updated.", vbInformation, "Process Complete"
        Else
            ' Handle other potential errors
            MsgBox "An error occurred: " & Err.Description, vbExclamation, "Error"
        End If
    Else
        MsgBox "No note was entered. The PayNote was not updated.", vbExclamation, "No Note Entered"
    End If
End Sub
You shouldn't need to add notes like that. If you keep a record of receipts in another table, then you can store the receiptID against all the invoices that were paid by that remittance. All you then need is the date and amount and customer in the receipts table.

If you have some invoices that don't get paid in full, then the above doesn't quite work, and you find you need another table called payment matching, say, so that you can match an invoice to 2 or 3 partial payments. Given an invoice you can display all the payments that were allocated to that invoice. Given a payment you can see all the payment allocations for that payment.

What you need is a slick form that can take a "amount to match" of say $2000, and let you click each invoice that's paid, and verify that the total selected invoices agrees to $2000. So you might need to part pay some invoices. You might need to allow users to add a settlement discount. You might need to add some options to automatch payments for a given month. Whatever helps users match the payment quickly and efficiently. You store the payments and allocation matching in a way that lets you review the matching subsequently. That's what you should be aiming for.
 
Last edited:
A heartfelt thank you to @arnelgp and @Gasman for your guidance and support! RecordsetClone Your advice improved my code significantly,
allowing me to update all invoices in a second. I truly appreciate your expertise and willingness to share your knowledge.

Thank you all for help!




Code:
Private Sub btnfill_Click()
Dim userNote As String
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim sql As String
    Dim InvoiceNumber As String
    Dim InvoiceID As Long

    ' Prompt the user to enter a note
    userNote = InputBox("Enter the note for PayNote:", "Add PayNote")

    ' Check if user entered a note or cancelled the input
    If userNote <> "" Then
        On Error GoTo ErrHandler

        ' Get the current database and the filtered recordset from the form
        Set db = CurrentDb()
        Set rs = Me.RecordsetClone

        ' Loop through each filtered record
        If Not rs.EOF Then
            rs.MoveFirst
            Do While Not rs.EOF
                ' Ensure the InvoiceNumber field exists and is valid
                If Not IsNull(rs!InvoiceNumber) Then
                    InvoiceNumber = rs!InvoiceNumber
                    
                    ' Get the InvoiceID from tblInvoice
                    InvoiceID = DLookup("InvoiceID", "tblInvoice", "InvoiceNumber = '" & InvoiceNumber & "'")
                    
                    ' Update the corresponding record in tblInvoice
                    sql = "UPDATE tblInvoice SET isPaid = True WHERE InvoiceNumber = '" & InvoiceNumber & "'"
                    db.Execute sql, dbFailOnError
                    
                    ' Update the corresponding record in tblNotes
                    sql = "UPDATE tblNotes SET PayNote = '" & userNote & "' WHERE InvoiceID = " & InvoiceID
                    db.Execute sql, dbFailOnError
                End If
                
                ' Move to the next record
                rs.MoveNext
            Loop
        End If
        
        ' Notify the user that the process is complete
        MsgBox "All filtered invoices have been updated.", vbInformation, "Process Complete"
        
        Exit Sub

ErrHandler:
        ' Handle potential errors
        MsgBox "An error occurred: " & Err.Description, vbExclamation, "Error"
    Else
        MsgBox "No note was entered. The PayNote was not updated.", vbExclamation, "No Note Entered"
    End If
End Sub
 
You never answered my questions. How do you know an invoice was paid today? How do you take that information to pick the records to update? If you have to touch each record you want to update anyway, why not just add a button to update the record instead of selecting it?

You don't need to use the recordset clone AND running an update query for each and every record you want to update is the most inefficient method you could have devised.

If you answer the questions we ask, we can usually offer the simplest solution.
Sorry @Pat Hartman for delay replay

How do you know an invoice was paid today? Examble Paid cash direct to my Boss 25 jul.24 ( its small company as personal company )

The Payment note just for him for dubel check .

why not just add a button to update the record instead of selecting it? sorry i dont know how ! can i ask to update one by one or for all records once !

I have QuickBooks also work side by side with this database .
I make this form just for organize data entry from other user and make print for spicfic invoices and Statements for customers with full description (1st QuickBooks has limit on (on description words i think 240 letter only & 2nd isssue and dosnt print full paid or full unpaid invoices once ) thats whay i make this database
 
Last edited:
But you don't update the records as a batch using a query or a code loop. You update each record as you touch it.

The point is, that since you have to go through the data to pick out the records you want to update one at a time, you should update the record AT THAT TIME. Instead, you seem to be flagging the records - which is an update. Then going back and finding all the records you updated and updating them again. That's pretty silly.

You're not updating enough records at any one time to worry about efficiency per se but you are making the job much harder than it needs to be.
Thank you @Pat Hartman
did i got your point or still not and code still silly 🤕🫣!

Code:
Private Sub btnfill_Click()
    Dim userNote As String
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim sql As String
    Dim InvoiceNumbers As String
    Dim InvoiceIDList As String
    Dim InvoiceID As Variant
    Dim noteExists As Variant

    ' Prompt the user to enter a note
    userNote = InputBox("Enter the note for PayNote:", "Add PayNote")

    ' Check if user entered a note or cancelled the input
    If userNote <> "" Then
        On Error GoTo ErrHandler

        ' Get the current database and the filtered recordset from the form
        Set db = CurrentDb()
        Set rs = Me.RecordsetClone

        ' Initialize the InvoiceNumbers and InvoiceIDList strings
        InvoiceNumbers = ""
        InvoiceIDList = ""

        ' Loop through each filtered record
        If Not rs.EOF Then
            rs.MoveFirst
            Do While Not rs.EOF
                ' Ensure the InvoiceNumber field exists and is valid
                If Not IsNull(rs!InvoiceNumber) Then
                    InvoiceNumbers = InvoiceNumbers & "'" & rs!InvoiceNumber & "', "
                End If
                ' Move to the next record
                rs.MoveNext
            Loop
        End If

        ' Remove the trailing comma and space
        If Len(InvoiceNumbers) > 0 Then
            InvoiceNumbers = Left(InvoiceNumbers, Len(InvoiceNumbers) - 2)
        End If

        ' Get the list of InvoiceIDs based on the InvoiceNumbers
        sql = "SELECT InvoiceID FROM tblInvoice WHERE InvoiceNumber IN (" & InvoiceNumbers & ")"
        Set rs = db.OpenRecordset(sql)
        If Not rs.EOF Then
            rs.MoveFirst
            Do While Not rs.EOF
                InvoiceIDList = InvoiceIDList & rs!InvoiceID & ", "
                rs.MoveNext
            Loop
        End If

        ' Remove the trailing comma and space
        If Len(InvoiceIDList) > 0 Then
            InvoiceIDList = Left(InvoiceIDList, Len(InvoiceIDList) - 2)
        End If

        ' Update the corresponding records in tblInvoice
        sql = "UPDATE tblInvoice SET isPaid = True WHERE InvoiceNumber IN (" & InvoiceNumbers & ")"
        db.Execute sql, dbFailOnError

        ' Loop through each InvoiceID to update or insert PayNote
        For Each InvoiceID In Split(InvoiceIDList, ", ")
            noteExists = DLookup("PayNote", "tblNotes", "InvoiceID = " & InvoiceID)
           
            If IsNull(noteExists) Or noteExists = "" Then
                ' Insert new note
                sql = "INSERT INTO tblNotes (InvoiceID, PayNote) VALUES (" & InvoiceID & ", '" & userNote & "')"
            Else
                ' Update existing note
                sql = "UPDATE tblNotes SET PayNote = '" & userNote & "' WHERE InvoiceID = " & InvoiceID
            End If
            db.Execute sql, dbFailOnError
        Next InvoiceID

        ' Notify the user that the process is complete
        MsgBox "All filtered invoices have been updated.", vbInformation, "Process Complete"

        Exit Sub

ErrHandler:
        ' Handle potential errors
        MsgBox "An error occurred: " & Err.Description, vbExclamation, "Error"
    Else
        MsgBox "No note was entered. The PayNote was not updated.", vbExclamation, "No Note Entered"
    End If
End Sub
 
Last edited:
There are a number of problems. You have gone very far with a bad schema and you've written a lot of code that shouldn't have had to be written to deal with the issues. The question is - how far are you willing to go to fix the problems? My problem of course is that I don't know why you made the design choices you made and so I cannot help you without spending hours understanding the business model and figuring out what the schema should have been.

I don't expect you to answer the questions. You need to think about them because your data model is full of holes and you need to plug some before moving on.

For example
-Why are Selling and Bills separate tables? I haven't looked at the queries or logic for any form except the one in question but you seem to have a field named Row which may be how you were intending to connect Bills and Selling. This is not how it should be done BTW.
-Why is Notes associated with Invoice rather than Selling (which should probably be named InvoiceItem)?
-Why are you only allowing one note per invoice? At a minimum, you would commonly have two payments. A down payment when the travel is booked and a final payment due before departure.
-What is the logic for handling the purchase currency for the InvoiceItem vs the payment currency received from the Customer? When do you do the conversion? Any application that deals with multiple currencies for transactions needs to have a single currency that is always converted from/to and some logic for the point in time when this happens. You also need currency conversions for each day for each currency you are working with. There are websites you can get a subscription to or even use for free if you only need a couple of transactions per day.
-Are your customers expecting a fixed price when you quote $150 for a room per night? Or, if the currency conversion rate changes, does the price change when they check in? I suppose, you could play the currency market and hope to make money on the conversion rate but it is probably better to be conservative and convert the supplier's prices when the customer places the order so you can give him a fixed price. OR, you can give him an estimate and then a final price before he makes the final payment. That covers you if the rates swing the wrong way for you.
-Why does the search form return a separate row for each item? Is the customer really paying individually by item?

As I said a long time ago, the logic in your button is totally wrong. Testing this app is a serious pain for me so it must be just as bad for you and that is probably why you haven't discovered the errors with your data. PERSONALLY, I never use popup forms except in rare cases. When I do, the popups are model as yours are but they do not interfere with testing. You may be familiar enough with all the objects to be OK with not ever being able to see two objects at once but I'm guessing that this design is keeping you from actually testing the application just as it kept me from testing. In order to make any progress at all, I had to change the properties of the menu form so I could get it out of the way to look at the tables or the code.

This is what you would have discovered had you headed my earlier advice and walked through the code to see what was actually happening. Here are some debug prints so you can see that the logic is not working.

print invoicenumbers - after the string is filled
'A00001', 'A00002', 'A00003', 'A00004', 'A00004', 'A00005', 'A00006', 'A00007', 'A00008', 'A00009', 'A00010', 'A00010', 'A00011', 'A00012', 'A00012',
print invoicenumbers - after the last two characters are removed
'A00001', 'A00002', 'A00003', 'A00004', 'A00004', 'A00005', 'A00006', 'A00007', 'A00008', 'A00009', 'A00010', 'A00010', 'A00011', 'A00012', 'A00012'
print invoiceidlist - after the string is filled
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
print invoiceidlist - after the last two characters are removed.
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50

So -
1. Your code says to search the filtered records. What are you doing to filter them?
2. There is absolutely no reason to use two loops. One to get the invoice name and the second to get the ID. The code isn't needed at all but if there really was some selection method, and you realllllllly like to write unnecessary code, you could use a single loop.
3. Here is the Update query -- As you can see, it is updating every row in the recordsource of the form because I didn't see how to filter it.
print sql
UPDATE tblInvoice SET isPaid = True WHERE InvoiceNumber IN ('A00001', 'A00002', 'A00003', 'A00004', 'A00004', 'A00005', 'A00006', 'A00007', 'A00008', 'A00009', 'A00010', 'A00010', 'A00011', 'A00012', 'A00012')
4. Here is the append query for the notes -- As you can see it is inserting a note for every row in the recordsource of the form?
print sql --- runs for each Id in InvoiceIDList
INSERT INTO tblNotes (InvoiceID, PayNote) VALUES (50, 'Pay note 1')

You can remove All the code from the button click event. All you have to do is click the paid checkbox for each row you want to mark paid. NO CODE IS REQUIRED at all - except for the notes. I can't give you advice on the notes at this point because the schema makes no sense so I don't know what you need to do. Normally, there is no reason to add a paid note. All the code in the BeforeUpdate event of the form should do is to add a paidDate to the record.

My advice - think about the answers to the questions. If you want help with fixing the schema, post the answers and we'll try to get the schema fixed. And just remove all that code from the click event and let Access just update the record because you checked the paid box.
Some time ago I coined the term "code wadding" to describe the approach in which a developer has to keep sticking new wads of code onto old procedures to accommodate each new problem generated by an inappropriate table design.

It comes from the old days in grade school when you could reach under the desk and feel wads and wads of gum left there by previous students. Eventually new wads get stuck onto old wads and the only way to fix it is to scrape off all of the old stuff .
 
Some time ago I coined the term "code wadding" to describe the approach in which a developer has to keep sticking new wads of code onto old procedures to accommodate each new problem generated by an inappropriate table design.

It comes from the old days in grade school when you could reach under the desk and feel wads and wads of gum left there by previous students. Eventually new wads get stuck onto old wads and the only way to fix it is to scrape off all of the old stuff .
So you're saying don't bring gum to class to begin with? Or if you do, swallow it (delete it).
 

Users who are viewing this thread

Back
Top Bottom