How to add blank lines to complete an Access report so that it matches a printed form? - forms

I deal with a bunch of government forms, and I constantly find myself trying to get Access 2013 to output a report that matches a pre-printed form.
Most of the forms are now in .pdf form, so Access doesn't handle them well (to my knowledge) without additional software, which I cannot install on user production computers.
So I usually re-create the form as an Access report, but I have real difficulty when I have only enough records for a half a page.
How do I get a report to print the required records, and then fill the page with blank records so the "form" looks correct?
I'd be willing to try any possible solution - I've even gone so far as to create blank records to try to make this work. My goal, however, is to automate this process so any user can generate the report and it prints out correctly without a bunch of fiddling.
On this form, some or all of the lines might be used, and each person (I have ~550 people who each have an individual form) has a different number of lines, depending on the number of jumps they have completed.

I would have a dummy table with just a single numeric field called id. Populate this with a number of records greater than the biggest number of 'extra' records you're ever going to need to fill your form, using numbers from 1 upwards for id. Now say your original record source query for the report is:
select field1, field2, field3, field4
from myTable
order by field1
and you always want 15 rows to fill your form, then change the report's record source to:
select top 15 sort_seq, field1, field2, field3, field4
from (
select 0 as sort_seq, field1, field2, field3, field4
from myTable
union
select id, null, null, null, null
from dummyTable
) as X
order by sort_seq, field1

Ok, so you are recreating the complete PDF form as Access report. And the lower part is the details section of a continuous report.
The easiest way is indeed to create the correct number of empty records, but do this automatically (e.g. using DAO Recordset.AddNew).
I like to use a separate dummy table for this (with the same structure as the original table) to not pollute the original table with dummy records.
The form has 20 lines, so if your original table has 7 records for the current person, insert 13 records into the dummy table, using a large PK so they get sorted to the bottom.
Then use a query as record source for the report, joining the master table with a UNION of Jumps and Dummy table.
Alternatively there is the Report.Line() method: https://msdn.microsoft.com/en-us/library/office/ff198297.aspx
But this will be a lot more complicated to get it right.

I hope you have found the solution to this issue. I am posting this in the hope that if someone else finds this same question they will find this one as the best possible solution, as I have been struggling for about 2 days now.
I suppose you have at least two tables and a query where you mix information from both, and your report is made out from this query. I will call this your Query1, so the trick is that when you open the Report from your Form, you will use the label number of your Form to make the filter of the Query1 from where you will get the information for your report. Like this:
rectotal = DCount("*", "Quote-Report", "[Quote_No]=" & stQtNo)
This instruction will count how many records you have on your “Quote-Report” Query that are of the same value of the [Quote_No] field inside your Quote Form , as you are assigning the stQtNo variable to the Form with the name “Quote”; and put this value on your rectotal variable.
Now with rectotal you need to determine how many lines you want in your report. I used 28 so the following formula will give how many blank rows needed to fill the page.
If rectotal / 28 - Int(rectotal / 28) = 0 Then
x = 28
Else
x = 28 * (rectotal / 28 - Int(rectotal / 28))
End If
blanklines = 28 - x
tLines = rectotal + blanklines
Of course you can use 15, 20 or as many rows you want your report to print per page. The variable blanklines will give you this number.
Then I use the recordset procedure from Access to open the table where I have the information that will print in the detail section of the report and fill with as many records as I need using the blanklines variable and the Do Until Loop.
Set db = CurrentDb
Set rs = db.OpenRecordset("Item_QD", dbOpenDynaset, dbseechanges)
Item_QD is the table
where I want to insert new records to fill the report detail section.
x = 0
rs.MoveLast
Do Until x = blanklines
rs.AddNew
rs![Quote_No] = stQtNo
rs![CODE No] = "Z" & x I use the Z and the number so I can erase the records once I don’t need them anymore.
x = x + 1
rs.Update
Loop
Please note that you need to Dim db As Database and Dim rs As Recordset variables outside your private sub in module header. Also your Access needs to be running these two classes so you can use them.
Finally once you open your report you have to go to your detail section and do a requery command to refresh all the rows prior to print. Need to format your detail cells in order to print the grid line and the rest is history.
The beauty of this procedure is that you can add and handle as many rows as you want or need inside your report and make a professional look of your quotes, packing slips, purchase orders, etc.
Hope this solves many problems. For a fast reference on how to use and manage ACCESS record set visit: https://www.youtube.com/watch?v=WNm17l54z1c
Private Sub Report_Open(Cancel As Integer)
Dim blanklines, tLines As Integer
Dim rectotal, x As Long
Dim stQtNo As String
Set db = CurrentDb
Set rs = db.OpenRecordset("Item_QD", dbOpenDynaset, dbseechanges)
stQtNo = Forms![Quote].[Quote_No]
rectotal = DCount("*", "Quote-Report", "[Quote_No]=" & stQtNo)
If rectotal / 28 - Int(rectotal / 28) = 0 Then
x = 28
Else
x = 28 * (rectotal / 28 - Int(rectotal / 28))
End If
blanklines = 28 - x
tLines = rectotal + blanklines
x = 0
rs.MoveLast
Do Until x = blanklines
rs.AddNew
rs![Quote_No] = stQtNo
rs![CODE No] = "Z" & x
x = x + 1
rs.Update
Loop
End Sub

Another solution. Code behind report. I use it for a simple report without grouping and row height doesn't change.
Option Compare Database
Option Explicit
Public totalCount As Integer
Private Sub Detail_Print(Cancel As Integer, PrintCount As Integer)
printBlankRecords getnumberofBlanks
End Sub
Public Sub printBlankRecords(numberBlanks As Integer)
Dim recordCount As Integer
recordCount = DCount("*", Me.RecordSource)
totalCount = totalCount + 1
If totalCount = recordCount Then
Me.NextRecord = False
'once you get to the last record, stay on last record
ElseIf totalCount > recordCount And totalCount < (recordCount + numberBlanks) Then
Me.NextRecord = False
'make the font and backcolor the same appearing to be empty record for each field
Me.RateID.ForeColor = Me.RateID.BackColor
End If
End Sub
Public Function getnumberofBlanks() As Integer
Dim recordCount As Integer
Dim N As Integer, X As Integer
X = 20
recordCount = DCount("*", Me.RecordSource)
If recordCount <= X Then
getnumberofBlanks = X - recordCount
Else
N = 1
Do While N * 21 + X <= recordCount
N = N + 1
Loop
getnumberofBlanks = (N * 21 + X) - recordCount
End If
End Function

Related

How to query a reference table for a value between a dates for a specified category in Apps Script?

I have a background in data analytics and have done a similar workflow in SQL but am brand new to Apps Script. I am a bit at a loss on where to even start in Apps Script. Any advice or pointing me in the direction of useful examples would be truly appreciated!
Currently, I have a reference table on one sheet with categories and values and the start and end date that value applies to. Then I have a data table on another sheet where I add an entry date and a category and I would like to have Apps Script write the corresponding value for that category on the date.
Reference table data (a blank end date means that is the current rate):
Category
Value
Start date
End date
A
25
01/01/2022
3/31/2022
B
40
01/01/2022
C
30
01/01/2022
A
15
04/01/2022
The data table where the entry date and the category are added manually over time. I want to use the reference table to write the value for that category for that entry date.
Entry Date
Category
Value
02/20/2022
B
40
02/27/2022
A
25
03/20/2022
A
25
04/16/2022
C
15
05/12/2022
A
30
06/02/2022
B
40
How do you get the query the reference data for that entry date and category to find the row with the corresponding value?
Description
As I said I'm not good at QUERY but I finally got something to work. I'm sure other can improve on it.
First I created a named range TestQuery for the table of data. I could have just as easily used range "A1:D6"
Next I fill in the End Date with =TODAY() so it has a date value. Then I build my query.
=QUERY(TestQuery,"select B where ( ( A = '"&B11&"' ) and ( date '"&TEXT(A11,"yyyy-mm-dd")&"' > C ) and ( date '"&TEXT(A11,"yyyy-mm-dd")&"' < D ) )")
Reference
Query Language
Compare Dates in Query
Getting data from a table on a sheet
function getData() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const values = sh.getRange("A2:D" + sh.getLastRow()).getValues();
Logger.log(JSON.stringify(values));//2d array
}
A2 is assumed to be the upper left corner of the data

Indicate more than one record with matching fields

How can I indicate multiple records with the same Invoice number, but a different Sales Person ID? Our commissions can be split into multiple Salespeople, so there can be two different Salespeople per an invoice.
For example:
Grouped by: Sales Person ID (No Changing this option)
These records are in the Group Footer.
Sales Person ID: Invoice: Invoice Amt: Commissions: (Indicator)
4433 R100 20,000 3,025 * More than one record on the same invoice with a different sales person
4450 R096 1,987 320
4599 R100 20,000 3,025 * More than one record on the same invoice with a different sales person
4615 R148 560 75
4777 R122 2,574 356
If your report has less than 1000 invoices, you may try something like this.
This will return true when a second ocurrence of the invoice shows up. Then you can make something like set the row background do red.
Global NumberVar Array invoices;
numbervar nextIndex := count(invoices) + 1;
if nextIndex <= 1000 and not ({Result.InvoiceNumber} in invoices) then (
redim invoices [nextIndex];
invoices[nextIndex] := {Result.InvoiceNumber};
true;
)
else false;
If you want to detect the first occurrence, you will need something more sophisticated.
I think a SQL Expression Field would be a good way to achieve the result you want. You already have an InvoiceNo in each row of data. You just need a SQL Expression Field that uses that INvoiceNo to execute a query to count the number of salespersons who get a commission.
Something along the lines of:
(
Select Count(Sales_Person_Id)
From [Table]
Where [Table].InvoiceNo = InvoiceNo
)
This will return an integer value that represents the number of salespersons who are associated with one invoice. You can either drop the SQL Expression Field in your Indicator column, or write some other formula to do something special.

Filter portal for most recently created record by group

I have a portal on my "Clients" table. The related table contains the results of surveys that are updated over time. For each combination of client and category (a field in the related table), I only want the portal to display the most recently collected row.
Here is a link to a trivial example that illustrates the issue I'm trying to address. I have two tables in this example (Related on ClientID):
Clients
Table 1 Get Summary Method
The Table 1 Get Summary Method table looks like this:
Where:
MaxDate is a summary field = Maximum of Date
MaxDateGroup is a calculated field = GetSummary ( MaxDate ;
ClientIDCategory )
ShowInPortal = If ( Date = MaxDateGroup ; 1 ; 0 )
The table is sorted on ClientIDCategory
Issue 1 that I'm stumped on: .
ShowInPortal should equal 1 in row 3 (PKTable01 = 5), row 4 (PKTable01 = 6), and row 6 (PKTable01 = 4) in the table above. I'm not sure why FM is interpreting 1Red and 1Blue as the same category, or perhaps I'm just misunderstanding what the GetSummary function does.
The Clients table looks like this:
Where:
The portal records are sorted on ClientIDCategory
Issue 2 that I'm stumped on:
I only want rows with a ShowInPortal value equal to 1 should appear in the portal. I tried creating a portal filter with the following formula: Table 1 Get Summary Method::ShowInPortal = 1. However, using that filter removes all row from the portal.
Any help is greatly appreciated.
One solution is to use ExecuteSQL to grab the Max Date. This removes the need for Summary functions and sorts, and works as expected. Propose to return it as number to avoid any issues with date formats.
GetAsTimestamp (
ExecuteSQL (
"SELECT DISTINCT COALESCE(MaxDate,'')
FROM Survey
WHERE ClientIDCategory = ? "
; "" ; "";ClientIDCategory )
)
Also, you need to change the ShowInPortal field to an unstored calc field with:
If ( GetAsNumber(Date) = MaxDateGroupSQL ; 1 ; 0 )
Then filter the portal on this field.
I can send you the sample file if you want.

Min value with GROUP BY in Power BI Desktop

id datetime new_column datetime_rankx
1 12.01.2015 18:10:10 12.01.2015 18:10:10 1
2 03.12.2014 14:44:57 03.12.2014 14:44:57 1
2 21.11.2015 11:11:11 03.12.2014 14:44:57 2
3 01.01.2011 12:12:12 01.01.2011 12:12:12 1
3 02.02.2012 13:13:13 01.01.2011 12:12:12 2
3 03.03.2013 14:14:14 01.01.2011 12:12:12 3
I want to make new column, which will have minimum datetime value for each row in group by id.
How could I do it in Power BI desktop using DAX query?
Use this expression:
NewColumn =
CALCULATE(
MIN(
Table[datetime]),
FILTER(Table,Table[id]=EARLIER(Table[id])
)
)
In Power BI using a table with your data it will produce this:
UPDATE: Explanation and EARLIER function usage.
Basically, EARLIER function will give you access to values of different row context.
When you use CALCULATE function it creates a row context of the whole table, theoretically it iterates over every table row. The same happens when you use FILTER function it will iterate on the whole table and evaluate every row against the filter condition.
So far we have two row contexts, the row context created by CALCULATE and the row context created by FILTER. Note FILTER use the EARLIER to get access to the CALCULATE's row context. Having said that, in our case for every row in the outer (CALCULATE's row context) the FILTER returns a set of rows that correspond to the current id in the outer context.
If you have a programming background it could give you some sense. It is similar to a nested loop.
Hope this Python code points the main idea behind this:
outer_context = ['row1','row2','row3','row4']
inner_context = ['row1','row2','row3','row4']
for outer_row in outer_context:
for inner_row in inner_context:
if inner_row == outer_row: #this line is what the FILTER and EARLIER do
#Calculate the min datetime using the filtered rows
...
...
UPDATE 2: Adding a ranking column.
To get the desired rank you can use this expression:
RankColumn =
RANKX(
CALCULATETABLE(Table,ALLEXCEPT(Table,Table[id]))
,Table[datetime]
,Hoja1[datetime]
,1
)
This is the table with the rank column:
Let me know if this helps.

Centura Gupta Team Developer Automation Possibility

Is there a automation tool which can automate the software build on Team Developer (v6.0).
I have tried with multiple automation tools to spy the table object in the application, it identifies it as Gupta ChildTable. But I am not able to retrieve the values from the row.
For example:
1. I have 10 rows in the table(grid) with 12 columns. I need to find the value "AAAAA" contained in first column and select that particular row via Automation.
2. I have 10 rows in the table(grid) with 12 columns. I need to find the value "AAAAA" contained in first column and click on particular cell in that row to input the data via Automation.
Thanks in advance.
Use VisTblFindString . This function ( and many others ) are included into your TD code if include 'VT.apl' in your include libraries .
VisTblFindString will return the Row - so then you simply set context to that row using SalTblSetContext( hWndForm, nRow ) , and then you can refer to the contents of each cell by name to return the value.
Syntax
nRow = VisTblFindString(hWndTable, nStartRow, hWndColumn, sString)
Handle: hWndTable
Number: nStartRow
Number: hWndColumn
String: sString
Description
Locates a string value within a column.
The string must match exactly, but case is ignored. Searching ends when the last row in the table is checked. A SAM_FetchRow message is sent for all rows that have not yet been fetched into the cache.
You can use the pattern matching characters understood by the function SalStrScan. The percent character (%) matches any set of characters. The underscore character ( _ ) matches any single character.
Parameters
hWndTable Table window handle.
nStartRow Row number at which to start the search.
hWndColumn Handle of column to search. Use hWndNULL to search all string columns.
sString String for which to search.
Return Value
Number: The row number if sString is found, or -1 if not found.
Example:
Set nRow = VisTblFindString (twOrders, 0, colDesc, 'AAAAAA')
Call SalTblSetContext( twOrders , nRow )
( Now you can get the value of any cell in nRow by referring to the Column Name )
e.g. Set sCellValue = twOrders.colDesc or Set sCellValue = twOrders.colId etc.
Rows ( or anything what-so-ever in a TableWindow - even the cell borders , backgrounds , lines, row headers etc ) can be treat as an 'Item' or 'Object' by TeamDeveloper . Recommend you use MTbl - it is an invaluable set of add-on functions that make dealing with Tables a breeze. I know of no sites using TableWindows that don't use MTbl. In terms of rows , you can define any row as an Item or Object and manipulate it accordingly. See M!Tbl ( a TableWindows extention ) and specifically fcMTblItem.DefineAsRow( hWndTbl, nRow ).
BTW , you can also use MTbl to completely change the look and feel of your TableWindows , to give them a real modern look.
Very rough napkin code, don't have TD on this computer. Not that you can copy&paste this easily anyway due to the code structure, only line by line.
tbl1 is the name of the table, col1 is the name of the column, substitute to fit your program.
Set nRow = TBL_MinRow
While SalTblFindNextRow( tbl1, nRow, 0, 0 )
Call SalTblSetContext( tbl1, nRow )
If tbl1.col1 = "AAAAA"
Call SalTblSetFocusCell( tbl1, nRow, tbl1.col1, 0, -1 )
Break
This should run through each row, check whether col1 has the chosen value, and then activates edit mode for that cell - provided the column is editable.