Académique Documents
Professionnel Documents
Culture Documents
"How do I update the Quantity On Hand each time an item is sold/used?" The simple answer is: You don't! You calculate the value when you need it. The calculation is very simple: the total number acquired, less the number disposed of. Just use DSum() on the table of acquisitions to get the number acquired for any product. Another DSum() expression on the table of uses/invoices gets the number disposed of. For the simplest databases, that's all you need. For heavily used databases, there are two practical problems:
1. Over the years, recalculating all values from the beginning becomes slow. 2. Outside the realm of pure maths, items go missing.
To solve the second problem, most companies do periodic stocktakes. The stocktake gives you a known starting point, so it solves the first problem too: you can limit your calculation to transactions since then. Storing the stocktake quantities is not denormalizing: any difference between the stored stocktake value and a pure calculated value is not a lack of data integrity. It's valuable information that may be worth reporting on. The quantity on hand is therefore: quantity as of the last stocktake, plus acquisitions since then, less disposals since then.
Even if you don't actually count stock, you can still take this approach to provide known values at a certain date, avoiding the need to recalculate everything from the beginning.
Sample Function
A typical database has an Invoice table and an Invoice Detail table, so one invoice can have many lines. (If this is unfamiliar, see the Order and Order Detail tables in the Northwind sample database.) In the same way, a single incoming delivery may include many products, so you need Acquisition and Acquisition Detail tables. With the Product table and Stocktake table, you have a structure like this:
This function returns the quantity on hand for any product, optionally at any date. Function OnHand(vProductID As Variant, Optional vAsOfDate As Variant) As Long 'Purpose: Return the quantity-on-hand for a product. 'Arguments: vProductID = the product to report on. ' vAsOfDate = the date at which quantity is to be calculated. ' If missing, all transactions are included. 'Return: Quantity on hand. Zero on error. Dim db As DAO.Database 'CurrentDb() Dim rs As DAO.Recordset 'Various recordsets. Dim lngProduct As Long 'vProductID as a long. Dim strAsOf As String 'vAsOfDate as a string. Dim strSTDateLast As String 'Last Stock Take Date as a string. Dim strDateClause As String 'Date clause to use in SQL statement. Dim strSQL As String 'SQL statement. Dim lngQtyLast As Long 'Quantity at last stocktake. Dim lngQtyAcq As Long 'Quantity acquired since stocktake. Dim lngQtyUsed As Long 'Quantity used since stocktake. If Not IsNull(vProductID) Then 'Initialize: Validate and convert parameters. Set db = CurrentDb() lngProduct = vProductID If IsDate(vAsOfDate) Then strAsOf = "#" & Format$(vAsOfDate, "mm\/dd\/yyyy") & "#" End If 'Get the last stocktake date and quantity for this product. If Len(strAsOf) > 0 Then strDateClause = " AND (StockTakeDate <= " & strAsOf & ")" End If strSQL = "SELECT TOP 1 StockTakeDate, Quantity FROM tblStockTake " & _ "WHERE ((ProductID = " & lngProduct & ")" & strDateClause & _ ") ORDER BY StockTakeDate DESC;" Set rs = db.OpenRecordset(strSQL) With rs If .RecordCount > 0 Then strSTDateLast = "#" & Format$(!StockTakeDate, "mm\/dd\/yyyy") & "#" lngQtyLast = Nz(!Quantity, 0) End If End With rs.Close 'Build the Date clause If Len(strSTDateLast) > 0 Then If Len(strAsOf) > 0 Then strDateClause = " Between " & strSTDateLast & " And " &
strAsOf
Else Else
If Len(strAsOf) > 0 Then strDateClause = " <= " & strAsOf Else strDateClause = vbNullString End If End If 'Get the quantity acquired since then. strSQL = "SELECT Sum(tblAcqDetail.Quantity) AS QuantityAcq " & _ "FROM tblAcq INNER JOIN tblAcqDetail ON tblAcq.AcqID = tblAcqDetail.AcqID " & _ "WHERE ((tblAcqDetail.ProductID = " & lngProduct & ")" If Len(strDateClause) = 0 Then strSQL = strSQL & ");" Else strSQL = strSQL & " AND (tblAcq.AcqDate " & strDateClause & "));" End If Set rs = db.OpenRecordset(strSQL) If rs.RecordCount > 0 Then lngQtyAcq = Nz(rs!QuantityAcq, 0) End If rs.Close 'Get the quantity used since then. strSQL = "SELECT Sum(tblInvoiceDetail.Quantity) AS QuantityUsed
" & _
"FROM tblInvoice INNER JOIN tblInvoiceDetail ON " & _ "tblInvoice.InvoiceID = tblInvoiceDetail.InvoiceID " & _ "WHERE ((tblInvoiceDetail.ProductID = " & lngProduct & ")" If Len(strDateClause) = 0 Then strSQL = strSQL & ");" Else strSQL = strSQL & " AND (tblInvoice.InvoiceDate " & strDateClause & "));" End If Set rs = db.OpenRecordset(strSQL) If rs.RecordCount > 0 Then lngQtyUsed = Nz(rs!QuantityUsed, 0) End If rs.Close 'Assign the return value OnHand = lngQtyLast + lngQtyAcq - lngQtyUsed End If Set rs = Nothing Set db = Nothing Exit Function End Function
The following code will Backup all Database Objects to a Folder specified by the Constant conPATH_TO_BKUPS (Line #3). You can change this if you wish, but be sure to add the Trailing Backslash since I did not test for this for the sake of brevity. The actual Back Up Database will be the Base Name of the Current Database minus the .mdb Extension, plus an Underscore (_), plus the Date formatted as mmddyyyy, plus .mdb. Each Backup will be unique only as far as a single Day is concerned and will DELETE a previous Backup for that Day should it exist. I intentionally placed the code in a Public Function where it can easily and directly be called from a Main Switchboard Item. Have fun, and if you have any questions, please feel free to ask.
Expand|Select|Wrap|Line Numbers
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.
Public Function fExportAllDBObjects() On Error Resume Next Const conPATH_TO_BKUPS As String = "C:\DB Backups\" Dim strAbsoluteBkUpPath As String Dim aob As AccessObject Dim strDBName As String Dim wrkSpace As Workspace Dim dbBackup As Database DoCmd.Hourglass True 'Retrive the Current Database Name only, strip out .mdb strDBName = Replace(Mid$(CurrentDb.Name, InStrRev(CurrentDb.Name, "\") + 1), ".mdb", "") 'Make the Backup DB Name unique for each Date strDBName = strDBName & "_" & Format$(Date, "mmddyyyy") & ".mdb" strAbsoluteBkUpPath = conPATH_TO_BKUPS & strDBName 'If a Bacup already exists for this Date, then DELETTE it If Dir$(strAbsoluteBkUpPath) <> "" Then Kill strAbsoluteBkUpPath End If 'Get Default Workspace. Set wrkSpace = DBEngine.Workspaces(0) 'Create the Database Set dbBackup = wrkSpace.CreateDatabase(strAbsoluteBkUpPath, dbLangGeneral) 'Export all Tables For Each aob In CurrentData.AllTables If Mid$(aob.Name, 2, 3) <> "Sys" Then
34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70.
DoCmd.TransferDatabase acExport, "Microsoft Access", strAbsoluteBkUpPath, _ acTable, aob.Name, aob.Name End If Next 'Export all Queries For Each aob In CurrentData.AllQueries DoCmd.TransferDatabase acExport, "Microsoft Access", strAbsoluteBkUpPath, _ acQuery, aob.Name, aob.Name Next 'Export all Forms For Each aob In CurrentProject.AllForms DoCmd.TransferDatabase acExport, "Microsoft Access", strAbsoluteBkUpPath, _ acForm, aob.Name, aob.Name Next 'Export all Reports For Each aob In CurrentProject.AllReports DoCmd.TransferDatabase acExport, "Microsoft Access", strAbsoluteBkUpPath, _ acReport, aob.Name, aob.Name Next 'Export all Macros For Each aob In CurrentProject.AllMacros DoCmd.TransferDatabase acExport, "Microsoft Access", strAbsoluteBkUpPath, _ acMacro, aob.Name, aob.Name Next 'Export all Modules For Each aob In CurrentProject.AllModules DoCmd.TransferDatabase acExport, "Microsoft Access", strAbsoluteBkUpPath, _ acModule, aob.Name, aob.Name Next DoCmd.Hourglass False End Function
P.S. - Just realized that you are using Access 2007. In that case, change the File Extension in Code Lines 12 (Comment), 13, and 16 to .accdb. jcnovosad re: How to make a backup of access 2003 database using a command button?
Join Date: Jan 2010 Posts: 4 #7: Feb 5 '10
Hi ADezzi, Many Thanks!!!! The code you posted works splendid!!!! Thanks again! Juan