So I'm a Novice to access and need help finishing my Inventory Database.
Currently I have the Following Tables and the field in them:
(Table: Fields, first field is primary key)
Items: Item#, Item Desc, Item Spec Reorder Level, Qty to Reorder, Qty on Hand
Materials_Used: ID(just an Autonumber field), Project, Item#, Amount
Projects: Project
Purchase_Orders: PONum, Received Date
Receiving_Amount: ID(again autonumber), Item#, Amount, PONum
I'm having issues in 2 area's:
Tying in PONum with Items Receiving_Amount: Currently I have a Purchase_Orders form which asks the user to input the date and POnum and a sub form of the Receiving amount in datasheet form. Thing is after I enter a date and PONum, and move down to entering the Items received I get and Enter Parameter Value box Purchase_Orders.ID and Purchase_Orders.PONumber
------ This issues is solved thanks to Wayne!
Having the Qty on Hand for the Items table to automatically update once an item is Received through the PO form (adding to it) and Materials Used form (Subtracting)
Database Relationship
Since you will be working in a multi-user update environment, you will need to make sure there are no conflicts with other users. The safest approach is to use a TRANSACTION.
Next, you need to decide how and when to make the updates to your two tables. Let's go with 'Option 1' which is to have a button the user clicks when they are finished. You would need to call the following subroutine when they click the button. You should also keep track if the user made any changes to the subform, but forgot to click the 'Save' button.
Then I strongly suggest keeping track of the changes you have made. For example, user enters qty of 5, saves changes. Tomorrow, looks at the data, sees the 5 and wants to change it to 6... that will corrupt the true inventory. One method to prevent is to have a 'flag' for the PO line items indicating they have been processed and preventing updates again.
The following code is just a sample... I coded what I think should be the input and output recordsets, but you will need to insure the correct input rows are selected to be processed and then select the output row(s) for the tables.
Look for the comments with ## in them.... fix the code to use YOUR control names.
Let me know if you need more of an explanation.
Option Compare Database
Option Explicit
' BeginTrans, CommitTrans, Rollback Methods Example
' After the BeginTrans method starts a transaction that isolates all the changes made,
' the CommitTrans method saves the changes.
' Notice that you can use the Rollback method to undo changes that you saved using
' the Update method. Furthermore, the main transaction is nested within another transaction
' that automatically rolls back any changes made by the user during this example.
' One or more table pages remain locked while the user decides whether or not to accept the changes.
' For this reason, make sure you only execute the transaction on some event - don't allow a user
' to be interactive, else he may go to lunch and may lock pages someone else needs!
' Add to: Receiving_Amount: ID(again autonumber), Item#, Amount, PONum
' Subtract from: Materials_Used: ID(just an Autonumber field), Project, Item#, Amount
Sub BeginTransX_Update_Inventory()
On Error GoTo Error_trap
Dim wrkDefault As DAO.Workspace
Dim dbs As DAO.Database
Dim tblInput As DAO.recordSet
Dim tblItems As DAO.recordSet
'Dim tblMaterials As DAO.recordSet
' Get default Workspace.
Set wrkDefault = DBEngine.Workspaces(0)
Set dbs = CurrentDb
' ## Change the following line to use the name of the form control that has your PONum
Set tblInput = dbs.OpenRecordset("select * from MaterialsRec where PONum = " & Me.txtPONum & ";") '<<< This will be the source of your changes. Can use a query to filter exact rows.
' Start transaction.
wrkDefault.BeginTrans
Do While Not tblInput.EOF
Set tblItems = dbs.OpenRecordset("select * from [Items] where [Item#] = " & tblInput![item] & ";") ' <<< This will be where the updates are applied.
' Increase Qty on Hand
tblItems.Edit
tblItems![Qty on Hand] = tblItems![Qty on Hand] + tblInput!Amount
tblItems.Update
'## Add a text field named 'ProcStatus' to table MaterialsRec, or delete the following update ... your choice...
tblInput.Edit
tblInput!ProcStatus = "updated"
tblInput.Update
tblInput.MoveNext
Loop
' You can remove the following code if desired...
' Ask if the user wants to commit to all the changes made above.
If MsgBox("Save changes?", vbYesNo) = vbYes Then
wrkDefault.CommitTrans
Else
wrkDefault.Rollback
End If
tblInput.Close
tblItems.Close
'tblMaterials.Close
Set tblItems = Nothing
'Set tblMaterials = Nothing
Set dbs = Nothing
Set wrkDefault = Nothing
Exit Sub
Error_trap:
wrkDefault.Rollback
MsgBox "An error was encountered, but all changes were rolled back." & vbCrLf & _
"Err: " & Err.Number & vbCrLf & _
"Desc: " & Err.Description
End Sub
Related
I want to change a date in a specific table to today's date by clicking a button in a related form. So all the button does is changing the date in a certain field in my DB. Is there a simple way to do this with VBA?
*Update
Well I wrote this in my VBA code:
CurrentDb.Execute "UPDATE Machines SET LastMaintenance = Date() WHERE MachineID = MachineID.Value"
With "Machines" being my table, "LastMaintenance" the column containing the date that has to be changed into today's date, "MachineID" the name of the record and "MachineID.Value" the name of the textbox bound to that same record.
When I click the button I get this error:
"Not enough parameters. 1 expected."
When performing an update query, you'll want to be cognizant of the datatype for each field, as you will have to present it differently in your code. Also, you will need to break up your string text when inserting a variable. In your current state, it's looking for a MachineID field with 'MachineID.value' as its contents. Try this:
CurrentDb.Execute "UPDATE Machines SET LastMaintenance = Date() WHERE MachineID = " & MachineID.Value
The most straightforward way is to run a UPDATE query.
CurrentDB.Execute "UPDATE someTable SET someDate = Date() WHERE stuff = 47"
If
a button in a related form
means a form bound to that table displaying the record you wish to update, use the OnClick event of the button:
Private Sub NameOfYourButton_Click()
Me![NameOfYourDateField].Value = Date
' Optionally, save the record at once:
Me.Dirty = False
End Sub
An UPDATE command is dangerous because you are making a change to the database assuming sane inputs. I would recommend using parameterized VBA code to avoid both SQL injection and throw an error in VBA for malformed inputs.
This example uses a static string to load your recordset, then it clearly states that the unverified input is only used in a Find command. Then it only acts if a matching record is found. This is a safer operation, albeit more verbose. It's also debuggable in VBA where the SQL UPDATE command is a kind of black box.
With CurrentDb.OpenRecordset("Machines", dbOpenDynaset)
.FindFirst "[MachineID]=" & CLng(MachineID.Value)
If .NoMatch Then
Debug.Print "ID not found: " & MachineID.Value
Else
.Edit
.Fields.Item("LastMaintenance").Value = Date()
.Update
End If
.Close
End With
I have a job-tracking system, and there is a query that returns results of all jobs that are overdue.
I have a form that displays each of these jobs one-by-one, and has two buttons (Job has been completed, and Job not completed). Not completed simply shows the next record.
I cannot find a way to get access to the current record to update it's contents if the "Has been Completed" button is pressed, the closest I can get is the long number which represents the records position in the form.
The VBA to get the index of the record in the form is as follows.
Sub Jobcompleted(frm As Form)
Dim curr_rec_num As Long
curr_rec_num = frm.CurrentRecord
End Sub
This is my first shot at VBA, and after an hour of searching I cannot find anything to solve my problem.
Am I going about this the entirely wrong way? Working in Microsoft Access 2007
Further Info All tables are normalized
Vehicle Table: Contains vehicle_id(pk), as well as rego and model etc
Job Table: Contains job_id(pk), vehicle_id(fk) and other info about what needs to happen, as well as the next occurance date, days between each occurance of the job (all jobs repeat) and other info
Job History Table: Contains job_history_id(pk), job_id(fk), date completed and comments
When the job completed button is pressed, it should create a new entry in the job history table with the current date, any comments and the job id
This is the script I am trying to get working
Private Sub Command29_Click()
Dim strSQL1 As String
Dim strSQL2 As String
Set Rs = CurrentRs
Set db = CurrentDb
strSQL1 = "INSERT INTO completed_jobs(JOB_ID, DATE_COMPLETED, COMMENTS) VALUES " & Rs!job.ID & ", " & Date
db.Execute strSQL1, dbFailOnError
strSQL2 = "UPDATE job SET JOB_NEXT_OCCURANCE = JOB_NEXT_OCCURANCE+JOB_RECURRANCE_RATE WHERE job.ID = Rs!job.ID"
db.Execute strSQL2, dbFailOnError
End Sub
Note: Line Set Rs = CurrentRs is completely incorrect, I believe this is what I need to figure out? This is called on button-press
I am posting an image which shows the form (non-continuous).
#HansUp, I get what you are saying, but I dont quite think it's applicable (I did not provide enough information first time around for you to understand I think)
#sarh I believe this Recordset that you are talking about is what I need, however I cannot figure out how to use it, any hints?
#Matt I am 90% sure I am using a bound form (Like I said, new to Access, been looking at everything people have suggested and learning as I go). There is of course an ID for the job (Just not shown, no need to be visible), but how would I access this to perform an operation on it? SQL I can do, integrating with Access/VBA I am new at
As I understand your situation, your form is data-bound bound (you can get record index), so - your form already located on this record. If you need to update some field of underlying dataset, you can write something like
Me!SomeField = ...
DoCmd.RunCommand acCmdSaveRecord
If your form has control bound to "SomeField", then the form will be updated automatically.
If this will not help, you can look to a couple of another directions:
1) Update records using SQL code. For example, you have ID of record that should be updated in the form data set, so you can write something like:
Call CurrentDB.Execute( _
"UPDATE SomeTable SET SomeField = SomeValue WHERE SomeTableID = " & Me!SomeTableID, dbSeeChanges)
2) You can look at the Bookmark property - both Recordset and Form has this property, it describes the record position. So you can write something like this (not the best example, but can help you to get an idea):
Dim Rs as Recordset
Set Rs = Me.RecordsetClone 'make a reference copy of the form recordset
Rs.Bookmark = Me.Bookmark 'locate this recordset to the form current record
Consider a simpler approach. I doubt you need to be concerned with the form's CurrentRecord property. And I don't see why you should need a command button for "Has been Completed" and another for "Has not been Completed".
Add a "Yes/No" data type field to the table which is used by your form's record source. Set it's default value property to 0, which represents False or No. Call it "completion_status". Create a new form using that record source. Then your form can have a check box control for completion_status.
Newly added records will have False/No as completion_status --- the check box will appear unchecked. The completion_status for other records in the forms can be toggled between Yes (checked) and No (unchecked) using the check box control.
I have a subform that is part of a larger form in which both contain a PROJECT_ID field. In the main form, the PROJECT_ID field is a key. In the subform, users have the option of a assigning a new representative to the project or making changes to the current project representatives in the subform. A project can have multiple representatives, however only one can be an active primary. My issue is I’m having trouble writing the validation for the primary flag field (ADV_FLAG) because it’s essentially based on a query. I’m a bit of a novice when it comes to VBA, but I think it’s probably the best solution for my issue. Any suggestions or samples of similar code on how to go about solving this issue. Below is currently what I have in the sub_form’s BeforeUpdate Event Procedure.
Private Sub Form_BeforeUpdate(Cancel As Integer)
'Validation for more than one Active Primary on Project
‘Append to the table T_Error_Catch the project_ID and an error_flag of YES
'where a project has more than one active primary.
DoCmd.SetWarnings False
DoCmd.OpenQuery "Q_Append_Errors_MultiplePrimaries", acViewNormal, acEdit
‘Throw an dialog box error to user to indicate this project already has one active primary
If ERR_FLAG = "Yes" And ADV_FLAG.Value <> "Secondary" Then
MsgBox "Project already has an Active Primary.", vbExclamation
ADV_FLAG.SetFocus
Cancel = True
End If
‘Truncate the T_Error_Catch table once the record has been corrected and there is again only one active primary
If ERR_FLAG = "Yes" And ADV_FLAG.Value = "Secondary" Then
DoCmd.SetWarnings False
DoCmd.OpenQuery "Clear T_Error_Catch", acViewNormal, acEdit
End If
End Sub
You're using form before update to validate ADV_FLAG values in existing records. I think you need to also address new records ... to prevent the user from adding a new representative as primary for a PROJECT_ID which already has a primary representative assigned. You could add a procedure for form before insert to deal with that.
However, rather than waiting until the user has completed all the fields before validating ADV_FLAG, do the validating in the after update event of the ADV_FLAG control. The after update procedure would handle both record update and insert.
But perhaps even easier to implement would be a command button on the subform which makes the current representative the sole primary for the current PROJECT_ID. The button's click event could use code such as this:
Dim strSql As String
Dim db As DAO.Database
On Error GoTo ErrorHandler
strSql = "UPDATE YourTable SET ADV_FLAG = 'Secondary' WHERE PROJECT_ID = " & _
Me.txtPROJECT_ID & ";"
Debug.Print strSql
Set db = CurrentDb
db.Execute strSql, dbFailOnError
Me.txtADV_FLAG = "Primary"
ExitHere:
On Error GoTo 0
Debug.Print "RecordsAffected: " & db.RecordsAffected
Set db = Nothing
Exit Sub
ErrorHandler:
'your error handler code here '
I assumed your subform has a text box control named txtPROJECT_ID which is bound to the PROJECT_ID field, and another named txtADV_FLG bound to the ADV_FLAG field. Change those names as required to match your data controls.
With this approach, there's not really a need for the user to directly edit values in txtADV_FLG (since the command button will make any changes needed). So in the property sheet for txtADV_FLG you can set Enabled = No, and optionally Locked = Yes.
The Debug.Print lines are to help you troubleshoot problems. They will print information to the Immediate Window. (You can go to the Immediate Window with the Ctrl+g keyboard shortcut.) After you have the code running correctly, you could disable or remove the Debug.Print statements. Or leave them as is ... you won't suffer any significant performance hit.
Notice I used db.Execute strSql, dbFailOnError instead of DoCmd.SetWarnings False and DoCmd.OpenQuery. I never SetWarnings off. If you do it, you must remember to SetWarnings back on again afterward. Your code didn't include DoCmd.SetWarnings True. So without SetWarnings on, you risk suppressing important information. Don't do that!
My eyes glazed over trying to follow #HansUp's solution, which I'm sure is correct. I'll instead offer an answer that uses the schema design to obviate any need to write much in the way of code.
I've had to do this many times -- you have a N:1 table but you want one of the records to be designated as PRIMARY.
First, you set up your N:1 table.
Then you add a field to the main table (the 1 side) and have that store the PK value of the record in the N table that you want as your main record.
For instance, say you have tblInventory and tblImage, which has an ImageID PK and an InventoryID FK.
To set one of those as the main image, you'd add a MainImageID field to tblInventory, and edit it with a combo box that lists the images from tblImage that are joined to that InventoryID in tblImage. You'd have to requery the combo box in the Inventory form's OnCurrent event, of course.
An example UI is here:
In that implementation, the list of images has a checkbox for TOP, but it's not editable (and that's a bad UI, since it's not clear from looking that it can't be changed there), but users figure it out fairly quickly. It's certainly not necessary that the check be displayed there, or that the same control be used to indicate the TOP item.
Sorry for the wall of text guys but this one requires expliaining, way too much code to post...
I'm importing fixed width files into access in methods that require data entry. I import the file using transferText into two specs (ones global, the other is special circumstance).
I have a function that uses DAO to cycle through all Field objects in TableDefs to build a duplicate table including an AutoIncrement PK so I have the ability to edit these records. I push the data into that table with INSERT INTO.
Works great. Errors are found, user goes into data entry to manually correct them which beats sifting through 400 character lines and reorganizing everything the way its supposed to be. Works great!
The problem: When the data entry changes are made a commit button is pressed which calls a function inside a module outside of the form. It closes the data entry form and pushes the information back to the original table minus the autoincremented PK, and is SUPPOSED to DROP the replicated table with ID's, and generate a new one searching once again for errors...
It pushes back to the original just fine, but it will not DROP the ID table. Always returns to me with a message indicating this table is locked. Ive noticed the table is indefiniatly locked down until all functions/subs exit. At any time stepping through the code I cannot delete it manually, once the execution has finished I am able to remove it.
I am assuming that since I called this through a command in the form, that the lock will not be released until all code finishes and the form terminate can be called and do its thing. Any thoughts? Yes this is very barbaric but it works quite well, I just need to be able to rip this other table off the planet so I can redrop an updated copy...
In the worst case I can make the user close the form out and hit another button in the main form but this is being designed heavily with user compitence in mind. However this now has my full attention and would like to at least find a solution even if it's not the optimal one.
-EDIT-
Two forms are used in this problem
FormA (Role: Load in and search for problems)
Examine button is pressed that:
- Uses TextTransfer based on predefined specs into tempExtract to
import the file
- DAO fires off on the Fields collection in tableDefs for
tempExtract, creates new table tempExtractID
- Performs searches through the file to find errors. Errors are saved to
a table Problem_t. Table contains Problem_ID (Set from the ID field
added to tempExtractID) and Description
- Execution of these tasks is successfully requerying the initial
form to showing a list of problems and number of occurances. A button
gains visibility, with onClick that opens the form DataEntry.
- At this point in the code after DAO execution, I can DROP the table
tempExtractID. DAO is NOT used again and was only used to build a new table.
FormB - Data Entry Form
As soon as I open this form, the table tempExtractID becomes locked and I cannot drop the table. The recordsource to the form querys tempExtractID against the ID's in Problems_t to return only what we need to key.
I cannot drop the table until the form has fully terminated. Button on the Data Entry form is pressed to commit changes, in which there are only 5 lines of code that get to fire off before I get my lock error.
*Xargs refers to the list of Field names pulled earlier through DAO. As DAO loops through Field objects, the physical names are added to an Xargs String which is placed in this table. Basically everything but the AutoNumber is being inserted back
docmd.Close acForm, "frmDataEntry", acSaveNo
call reInitializeExtract
> docmd.RunSQL "DELETE FROM tempExtract"
> docmd.RunSQL "INSERT INTO tempExtract SELECT (" & DLookup("Value", "CONFIG_t", "Item = 'Xargs'" & ") FROM tempExtractID"
docmd.DeleteObject acTable, "tempExtractID"
This is the only code that is run between the time where the form is opened (Where the table first gets locked) and continues to be locked until all subs & functions have completed.
I suggest setting the recordsource of the form to vbNullString and then deleting the table. This should work, unless you also have comboboxes and so forth bound to this table.
Without code it's hard to say, but if you're using DAO, you need to clean up your code objects. That means setting to Nothing your database objects, and closing and setting to Nothing any recordset objects.
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = DBEngine.OpenDatabase("[path to database]")
Set rs = db.OpenRecordset("[SELECT statement]")
rs.Close
Set rs = Nothing
db.Execute("[DML or DDL statement]", dbFailOnError)
db.Close
Set db = Nothing
Set db =CurrentDB
Set rs = db.OpenRecordset("[SELECT statement]")
rs.Close
Set rs = Nothing
Set db = Nothing ' you don't close a db variable initialized with CurrentDB
While VBA is supposed to clean up these objects when they go out of scope, it's not 100% reliable (because VBA uses reference counting to keep track of whether an object can be released, and it doesn't always know when all the references have been cleared).
Objects left open is the most likely source of the locks, so you should make sure you're cleaning up your object variables after you've finished with them.
EDIT after seeing that you're using DoCmd.RunSQL:
Using DoCmd.RunSQL is likely the cause of the problem. It is certainly something that takes away your programmatic management of your connections. If you use DAO instead, you'll have control over the connection, as well as avoiding the real pitfall of DoCmd.RunSQL, which is that it doesn't handle errors. If a DML or DDL statement cannot complete successfully in full, the whole thing should fail. For example, if you're appending 100 records and 10 of them fail for key violations, DoCmd.RunSQL will transparently append the 90 and NOT REPORT THE 10 FAILURES. It's the same with updates and any other DML/DDL statement. DoCmd.RunSQL "helpfully" silently completes as many of the updates as it can, leaving you having no idea that some of it failed to complete.
Granted, in some cases you might want that to happen, e.g., if you're appending records that you know might have PK collisions and don't want to spend the CPU cycles on an outer join that eliminates the duplicates from the set of records you're appending.
But most of the time, that is not the case.
As I said in my comment above, I use a function that is designed to transparently replace DoCmd.RunSQL and uses a DAO Execute statement and error handling. I have posted it a couple of times on SO (here's one), and here's the version I have in production use in my currently most-active development project:
Public Function SQLRun(strSQL As String, Optional db As Database, _
Optional lngRecordsAffected As Long) As Long
On Error GoTo errHandler
Dim bolCleanup As Boolean
If db Is Nothing Then
Set db = CurrentDb
bolCleanup = True
End If
'DBEngine.Workspaces(0).BeginTrans
db.Execute strSQL, dbFailOnError
lngRecordsAffected = db.RecordsAffected
'DBEngine.Workspaces(0).CommitTrans
exitRoutine:
If bolCleanup Then
Set db = Nothing
End If
SQLRun = lngRecordsAffected
'Debug.Print strSQL
Exit Function
errHandler:
MsgBox "There was an error executing your SQL string: " _
& vbCrLf & vbCrLf & Err.Number & ": " & Err.Description, _
vbExclamation, "Error in SQLRun()"
Debug.Print "SQL Error: " & strSQL
'DBEngine.Workspaces(0).Rollback
Resume exitRoutine
End Function
(the transactions are commented out because they were causing problems that I didn't have time to troubleshoot)
You could replace these lines of yours:
DoCmd.RunSQL "DELETE FROM tempExtract"
DoCmd.RunSQL "INSERT INTO tempExtract SELECT (" _
& DLookup("Value", "CONFIG_t", "Item = 'Xargs'" & ") FROM tempExtractID"
...with this:
SQLRun "DELETE FROM tempExtract"
SQLRun "INSERT INTO tempExtract SELECT (" _
& DLookup("Value", "CONFIG_t", "Item = 'Xargs'" & ") FROM tempExtractID"
You could also do this:
Debug.Print SQLRun("DELETE FROM tempExtract") & " records deleted."
Debug.Print SQLRun("INSERT INTO tempExtract SELECT (" _
& DLookup("Value", "CONFIG_t", "Item = 'Xargs'" _
& ") FROM tempExtractID") & " records inserted."
Since the function returns the .RecordsAffected for each Execute, you can print to the Immediate Window, or you could assign the return value to a variable, or pass an existing variable through to it and work with that variable thus:
Dim lngRecordsAffected As Long
...
Call SQLRun("DELETE FROM tempExtract", , lngRecordsAffected)
Debug.Print lngRecordsAffected & " records deleted."
Call SQLRun("INSERT INTO tempExtract SELECT (" _
& DLookup("Value", "CONFIG_t", "Item = 'Xargs'" _
& ") FROM tempExtractID", , lngRecordsAffected)
Debug.Print lngRecordsAffected & " records inserted."
The point is that if there are errors on the Execute statement, the whole thing will fail (and pop up an error message -- you might want to change it so that if there's an error it returns -1 or some such instead of popping an MsgBox).
I use this function most often by passing in a pre-cached database variable, so I don't want to clean it up afterwards. If you're using a different database other than CurrentDB(), you really do want to make sure any database variable pointing to your external db is closed and set to Nothing. Without that, locks are maintained on the top-level database objects, and the LDB file remains open and active.
I'm pretty new to MS Access. I'm trying to create a simple form that will basically search for a particular record using a textbox, rather than a drop down box. Essentially a user would be able to enter an ID number and retrieve some other related Info. However, I do not want the user to be able to add any new records to the database. I've been able to get the forms to look the way I want them, but I'm not sure where to place the code (do I create a macro, insert the code into the properties of the button?) Any help is greatly appreciated!
I assume that you have bound your form to a table or a query and that you want to be able to enter the ID manually in a textbox, then press ENTER and load that record's data or display an error message if there is no such record.
As dsteele said, make sure that the form's Data property Allow Addtions is set to No to disallow users from adding records.
Then, from the AfterUpdate event of the textbox, add the following code (assuming that your textbox is named txtGoTo):
Private Sub txtGoTo_AfterUpdate()
If (txtGoTo & vbNullString) = vbNullString Then Exit Sub
Dim rs As DAO.RecordSet
Set rs = Me.RecordsetClone
rs.FindFirst "[ID]=" & txtGoTo
If rs.NoMatch Then
MsgBox "Sorry, no such record '" & txtGoTo & "' was found.", _
vbOKOnly + vbInformation
Else
Me.RecordSet.Bookmark = rs.Bookmark
End If
rs.Close
txtGoTo = Null
End Sub
Note that you will have to change the line rs.FindFirst "[ID]=" & txtGoTo to something that is adequate for your data:
"[ID]=" should be replaced by the field you want to search (it could be "[POReference]=" or something else.
if you are searching by a numeric ID, for instance because the field is an autonumber column, then the code is fine.
Otherwise, if the field you are searching on is a string (say PN12-G) then you have to change the code to:
rs.FindFirst "[ID]=""" & txtGoTo & """"
Failing to use the proper quoting (or quoting where not necessary) will result in errors of the kind Data type mismatch....
As a new user, I would recommend that you have a look at the sample NorthWind project database that is either shiped with older versions of Access or available as a template for download from Access 2007.
There a lots of techniques to learn from as a new Access developer, including other ways to implement record navigation.
Set the form property Data/'Allow Additions' to No.
Either in the AfterUpdate event of the textbox, or in the Click event of a button, you can write code or assign a macro to look up and display the record you want.