1|*
2|**************************************************************************
3|* Copyright (c) 2004 Schellenbach & Associates, Inc. dba AccuSoft *
4|* Enterprises as an unpublished work. Permission is hereby granted, free *
5|* of charge, to any person obtaining a copy of this software, to use the *
6|* software without restriction, including without limitation the rights *
7|* to use, copy, modify, merge, publish or distribute the software, and *
8|* to permit persons to whom the software is furnished to do so, subject *
9|* to the following conditions: This copyright notice and permission *
10|* notice shall be included in all copies or substantial portions of the *
11|* software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY *
12|* KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES *
13|* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND *
14|* NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR *
15|* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
16|* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION *
17|* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
18|**************************************************************************
19|**************************************************************************
20|*
21|* USER INTERFACE EXAMPLE PROGRAM 4
22|*
23|**************************************************************************
24|**************************************************************************
25|*
26|* This example illustrates a character-based interface using the AccuSoft
27|* Smart User Interface library for data entry. Most display and input
28|* processing is handled by the SUI subroutines, rather than using PRINT
29|* and INPUT statements. By using the SUI library, input fields accept
30|* cursor keys, editing keys, function keys and mouse clicks. The SUI
31|* subroutines also utilize AccuTerm Visual Styles to display screen
32|* elements using a Windows-like look (if avaliable).
33|*
34|**************************************************************************
35|*
36|* To use the SUI library, you must include SUI.TERMDEF. This INCLUDE item
37|* contains EQUates for various terminal functions like visual attributes
38|* and line graphics, as well as constants for the keyboard commands like
39|* TERM.ENTER$ or TERM.PGUP$.
40|*
41|$INCLUDE SUIBP SUI.TERMDEF
42|*
43|**************************************************************************
44|*
45|* The CUST.REC INCLUDE item declares the CUST.REC array, which is used to
46|* store a working copy of the current data record. It also defines the
47|* record layout by equating field names to array positions in the CUST.REC
48|* array.
49|*
50|$INCLUDE UIEX.CUST.REC
51|*
52|**************************************************************************
53|*
54|* EQUates for control characters and delimiters and other constants
55|*
56|EQU VM TO CHAR(253)
57|EQU BEL TO CHAR(7)
58|EQU ESC TO CHAR(27)
59|EQU STX TO CHAR(2)
60|EQU CR TO CHAR(13)
61|EQU FALSE TO 0
62|EQU TRUE TO 1
63|*
64|**************************************************************************
65|*
66|* Any routine that uses the SUI library must call SUI.GET.TERM to
67|* initialize the TermDef array. The TermDef array is a required argument
68|* for all of the SUI subroutines. It is much better to call this routine
69|* one time and pass the TermDef array between various routines instead of
70|* calling it in each application subroutine that uses SUI.
71|*
72|CALL SUI.GET.TERM(MAT TermDef)
73|*
74|**************************************************************************
75|*
76|* Once the TermDef array is initialized by SUI.GET.TERM, the TERM.RESET
77|* string is sent to the terminal to initialize the terminal state. The
78|* reset string programs necessary function keys and initalizes any other
79|* terminal state as required for each particular terminal type.
80|*
81|PRINT TERM.RESET:
82|*
83|**************************************************************************
84|*
85|* This program uses AccuTerm Imaging to display pictures associated with
86|* records in the customer file. This EQUate defines the path where the
87|* image files are located.
88|*
89|EQU PIX.PATH TO 'http://www.asent.com/demo/PIX/'
90|*
91|* Check for image support (no images if dumb terminal or AccuTerm Lite)
92|*
93|IF INDEX(TERM.SPECIAL,'I',1) THEN IMGFLG = TRUE ELSE IMGFLG = FALSE
94|*
95|**************************************************************************
96|*
97|* Open our files...
98|*
99|OPEN 'CUST.SAMPLE' TO FN.CUST ELSE PRINT 'NO CUST.SAMPLE FILE'; STOP
100|OPEN 'DICT CUST.SAMPLE' TO FN.DICT.CUST ELSE PRINT 'NO DICT CUST.SAMPLE FILE'; STOP
101|OPEN 'CUST.SAMPLE.XREF' TO FN.CUST.XREF ELSE PRINT 'NO CUST.SAMPLE.XREF'; STOP
102|*
103|**************************************************************************
104|*
105|* Define the data field control structures. NUMFLDS is the number of
106|* fields. The CONTROL array defines the field control elements such as
107|* field label, label position, field position and size, and field prompt.
108|* The IDATA array stores the current field values, and is initialized
109|* from the CUST.REC array when a data record is read from the file. When
110|* the record is updated, values are copied from the IDATA array to the
111|* CUST.REC array and the CUST.REC array is passed to the CUST.UPDATE
112|* subroutine to update the data and index files (a separate subroutine is
113|* used to update the file since the update process may handle other
114|* functions like updating indexes).
115|*
116|NUMFLDS = 12
117|DIM CONTROL(12)
118|DIM IDATA(12)
119|*
120|* Define field control elements
121|* value 1: field label text
122|* value 2: field label column
123|* value 3: field label row
124|* value 4: field label width (0 for actual width, no padding)
125|* value 5: field data column
126|* value 6: field data row
127|* value 7: field data width
128|* value 8: field data height
129|* value 9: field prompt message
130|CONTROL(1) = 'Customer IDý7ý2ý20ý28ý2ý30ý1ýEnter the ID, or name to search for, or N for next number'
131|CONTROL(2) = 'Contactý7ý4ý20ý28ý4ý30ý1ýEnter the contact name'
132|CONTROL(3) = 'Company nameý7ý5ý20ý28ý5ý30ý1ýEnter the company name'
133|CONTROL(4) = 'Address line 1ý7ý6ý20ý28ý6ý30ý1ýEnter address line 1'
134|CONTROL(5) = 'Address line 2ý7ý7ý20ý28ý7ý30ý1ýEnter address line 2'
135|CONTROL(6) = 'Cityý7ý8ý20ý28ý8ý25ý1ýEnter the city'
136|CONTROL(7) = 'State or provinceý7ý9ý20ý28ý9ý15ý1ýEnter the state or province abbreviation'
137|CONTROL(8) = 'Zip/postal codeý7ý10ý20ý28ý10ý10ý1ýEnter the zip or postal code'
138|CONTROL(9) = 'Countryý7ý11ý20ý28ý11ý18ý1ýEnter the country'
139|CONTROL(10) = 'Phoneý7ý12ý20ý28ý12ý15ý1ýEnter the phone number'
140|CONTROL(11) = 'Faxý7ý13ý20ý28ý13ý15ý1ýEnter the fax number'
141|CONTROL(12) = 'Notesý7ý14ý20ý28ý14ý30ý3ýEnter any notes about this customer'
142|*
143|**************************************************************************
144|*
145|* Define some screen control strings for prompts & errors
146|*
147|PROMPT ''
148|PL = @(5,22):TERM.CEOL
149|EL = @(5,23):TERM.CEOL
150|*
151|**************************************************************************
152|*
153|* This program processes one customer record at a time, and is organized
154|* using three nested loops. The outermost loop (the RECORD loop) is
155|* executed once for each record accessed. The loop repeats until the EXIT
156|* control variable is set to TRUE.
157|*
158|EXIT = FALSE
159|LOOP UNTIL EXIT DO
160| *
161| *************************************************************************
162| *
163| * Before prompting for the customer ID, reset the internal data array
164| * (IDATA) and CUST.ID variables, then clear the screen and display the
165| * heading and field labels.
166| *
167| GOSUB RESTART
168| GOSUB DSPSCRN
169| *
170| *************************************************************************
171| *
172| * The middle loop is the ACTION loop. It is executed for the current
173| * record until the user performs an action that terminates processing of
174| * that record, such as exiting, cancelling, saving or deleting the
175| * record. The field number to begin prompting (NXTFLD) is initialized to
176| * 1, causing the ID field to be prompted first. The loop immediately
177| * enters the PROMPT loop, followed by the field modification prompt. The
178| * loop repeats until the DONE control variable is set to TRUE.
179| *
180| NXTFLD = 1
181| DONE = FALSE
182| LOOP
183| *
184| ************************************************************************
185| *
186| * The inner loop is the PROMPT loop. It is executed for each prompt
187| * field as specified by the NXTFLD variable. The prompt loop simply
188| * calls the local INPUT.FIELD subroutine which performs field input,
189| * data validation and keyboard command decoding. The FIELD loop repeats
190| * until the field number is greater than the number of fields, or until
191| * the DONE control variable is set to TRUE. The DONE control variable
192| * may be set to TRUE in the INPUT.FIELD subroutine, if the user enters a
193| * NULL to for the item-ID.
194| *
195| LOOP
196| CURFLD = NXTFLD
197| UNTIL DONE OR CURFLD > NUMFLDS DO
198| *
199| ***********************************************************************
200| *
201| * Prompt for the next field. Update the NXTFLD variable with the field
202| * number to prompt next, taking into account cursor keys and mouse
203| * clicks.
204| *
205| GOSUB INPUT.FIELD
206| REPEAT ;* end of PROMPT loop
207| *
208| ************************************************************************
209| *
210| * If the FIELD loop exited with the DONE control variable set to TRUE,
211| * bypass the modification prompt because no action is required.
212| * Otherwise, prompt for which field to modify, or other actions such as
213| * save or delete.
214| *
215| IF NOT(DONE) THEN
216| *
217| NXTFLD = NUMFLDS + 1 ;* assume we need to reprompt for action
218| *
219| PRINT PL:'Enter field number to modify or FI to save, DE to delete, EX to exit: ':
220| INPUT ANS:
221| PRINT EL:
222| *
223| ***********************************************************************
224| *
225| * Decode the response
226| *
227| ANS = OCONV(ANS, 'MCU')
228| BEGIN CASE
229| CASE NUM(ANS) AND ANS >= 1 AND ANS <= NUMFLDS; NXTFLD = ANS
230| CASE ANS EQ 'FI'; GOSUB SAVE.RECORD
231| CASE ANS EQ 'DE'; GOSUB DELETE.RECORD
232| CASE ANS EQ 'EX' OR ANS EQ ''; GOSUB CHECK.EXIT
233| END CASE
234| *
235| END
236| *
237| UNTIL DONE DO REPEAT ;* end of ACTION loop
238| *
239|REPEAT ;* end of RECORD loop
240|*
241|**************************************************************************
242|*
243|* All done - clear the screen, restore the cursor and exit!
244|PRINT TERM.CLEAR:TERM.CSRON:
245|STOP
246|*
247|*
248|**************************************************************************
249|**************************************************************************
250|* LOCAL SUBROUTINES
251|**************************************************************************
252|**************************************************************************
253|*
254|*
255|**************************************************************************
256|*
257|* The INPUT.FIELD subroutine is the main prompting routine. This routine
258|* displays the prompt string (from the CONTROL array), and enters a loop,
259|* prompting for a specified field (CURFLD) and setting the next field
260|* variable (NXTFLD) appropriately. The loop repeats until the NXTFLD
261|* (initially set to CURFLD) changes. This causes the field prompt to be
262|* repeated in case unrecognized keys are pressed (for example, the PGUP
263|* key), or invalid data is entered (illegal customer ID, etc.) If a NULL
264|* is entered for the ID field, and there is no current record, the ACTION
265|* loop control variable, DONE, and the RECORD loop control variable, EXIT,
266|* are set to TRUE, and this routine exits. A mouse click is handled by
267|* setting the NXTFLD variable to the field number that was clicked.
268|*
269|INPUT.FIELD:
270|*
271|**************************************************************************
272|*
273|* Display the prompt message
274|*
275|CALL SUI.DISPLAY.LABEL(5, 22, 74, 'L', 0, CONTROL(CURFLD)<1,9>, CMD, MAT TermDef)
276|*
277|**************************************************************************
278|*
279|* Initialize current value, next field & character positions
280|*
281|PREVAL = IDATA(CURFLD) ;* Save previous field value to detect change in value
282|NXTFLD = CURFLD ;* Assume field number not changed
283|BEGPOS = 0 ;* Set initial text display character position
284|CURPOS = 0 ;* Set initial text cursor character position
285|*
286|**************************************************************************
287|*
288|* Prompt for this field until NXTFLD variable is updated
289|*
290|LOOP
291| *
292| *************************************************************************
293| *
294| * Prompt for input with full text editing functions
295| *
296| CALL SUI.INPUT.TEXT(CONTROL(CURFLD)<1,5>, CONTROL(CURFLD)<1,6>, CONTROL(CURFLD)<1,7>, CONTROL(CURFLD)<1,8>, '', 0, IDATA(CURFLD), BEGPOS, CURPOS, CMD, MAT TermDef)
297| PRINT TERM.CSRON:EL: ;* Turn cursor back on & clear the error line
298| *
299| *************************************************************************
300| *
301| * Decode returned CMD
302| *
303| BEGIN CASE
304| *
305| ************************************************************************
306| *
307| * Check for ENTER, TAB, UP, DOWN or mouse click
308| *
309| CASE CMD EQ TERM.ENTER$ OR CMD EQ TERM.TAB$ OR CMD EQ TERM.DOWN$
310| NXTFLD = CURFLD + 1; * Move to next field
311| CASE CMD EQ TERM.STAB$ OR CMD EQ TERM.UP$
312| IF CURFLD > 1 THEN NXTFLD = CURFLD - 1 ;* Move to previous field
313| CASE CMD EQ TERM.LEFTBUTTON$
314| GOSUB CHECK.MOUSE ;* Move to clicked-on field
315| IF CURFLD EQ 1 AND NXTFLD NE CURFLD THEN
316| IF IDATA(1) EQ '' THEN NXTFLD = CURFLD ;* Mouse click in invalid field - ignore click
317| END
318| *
319| END CASE
320| *
321| *************************************************************************
322| *
323| * Check for any special values (like 'END' or 'EXIT')
324| *
325| IF OCONV(IDATA(CURFLD),'MCU') EQ 'END' THEN
326| IDATA(CURFLD) = PREVAL ;* Restore previous value
327| GOSUB CHECK.ABANDON ;* Ensure OK to loose changes
328| IF OK THEN
329| *
330| ***********************************************************************
331| *
332| * No changes, or user OKs abandoning them, so we are outa here!
333| *
334| DONE = TRUE
335| EXIT = TRUE
336| RETURN
337| *
338| END ELSE
339| *
340| ***********************************************************************
341| *
342| * User does not want to abandon changes, so refresh previous value &
343| * reprompt
344| *
345| XLINE = CURFLD ;* Field number to refresh
346| GOSUB DSPLINE ;* Redisplay the previous value
347| NXTFLD = CURFLD ;* Reprompt
348| *
349| END
350| END
351| *
352| *************************************************************************
353| *
354| * Peform field data validation if next field number changed
355| *
356| IF NXTFLD NE CURFLD THEN
357| BEGIN CASE
358| *
359| CASE CURFLD EQ 1
360| *
361| **********************************************************************
362| *
363| * Validate the ID field. If NULL, quit. If changed, read new record.
364| *
365| IF CUST.ID EQ '' AND IDATA(CURFLD) EQ '' THEN
366| DONE = TRUE
367| EXIT = TRUE
368| RETURN
369| END
370| *
371| IF IDATA(CURFLD) NE PREVAL THEN
372| ID = IDATA(CURFLD)
373| GOSUB CHECK.ID ;* Check the newly entered ID handling index lookups
374| IF OK THEN
375| *
376| ********************************************************************
377| *
378| * New ID (or result of index lookup) is good!
379| *
380| IDATA(CURFLD) = ID ;* Update the current field value in case of index lookup
381| *
382| END ELSE
383| *
384| ********************************************************************
385| *
386| * New ID is invalid
387| *
388| IDATA(CURFLD) = PREVAL ;* Restore previous value
389| XLINE = CURFLD ;* Field number to refresh
390| GOSUB DSPLINE ;* Redisplay the previous value
391| NXTFLD = CURFLD ;* Reprompt
392| *
393| END
394| END
395| *
396| END CASE
397| END
398| *
399|WHILE CURFLD EQ NXTFLD DO REPEAT
400|*
401|RETURN
402|*
403|*
404|**************************************************************************
405|*
406|* The CHECK.EXIT subroutine checks if any field data has changed, and
407|* prompts if the user wants to abandon changes. If no changes, or the
408|* user decides to abandon the changes, the DONE and EXIT loop control
409|* variables are set to TRUE, causing all three loops to terminate, and the
410|* program itself to exit.
411|*
412|CHECK.EXIT: *
413|*
414|GOSUB CHECK.ABANDON
415|IF OK THEN
416| DONE = TRUE
417| EXIT = TRUE
418| GOSUB HIDEPIX
419|END
420|RETURN
421|*
422|*
423|**************************************************************************
424|*
425|* The CHECK.ID subroutine validates a newly entered item-ID. If the
426|* current record has unsaved changes, the user is prompted to abandon the
427|* changes. If no changes, or the user abandons the changes, and the new ID
428|* is not NULL, an attempt is made to read a record using the new ID. If
429|* the read is not successful, the ID is assumed to be a search string, and
430|* the search subroutine is called to select an ID based on the search
431|* string. If a valid ID is returned (or if one was initially entered), the
432|* new record data is displayed. If the new ID is null, or if the search
433|* routine did not return a valid ID, a warning message is displayed and
434|* the OK indicator variable is set to FALSE. Otherwise it is set to TRUE.
435|*
436|CHECK.ID: *
437|*
438|**************************************************************************
439|*
440|* Make sure we don't have any unsaved data before changing the ID
441|*
442|GOSUB CHECK.ABANDON
443|IF NOT(OK) THEN RETURN ;* Reprompt for the ID
444|IF ID EQ '' THEN
445| OK = FALSE ;* NULL is not a valid ID!
446|END ELSE
447| *
448| *************************************************************************
449| *
450| * Check if user wants new ID
451| *
452| IF ID EQ 'N' OR ID EQ 'n' THEN
453| * Get next sequential ID
454| READVU ID FROM FN.DICT.CUST,'NEXT',2 THEN
455| WRITEV ID + 1 ON FN.DICT.CUST,'NEXT',2
456| GOSUB RESTART
457| IDATA(1) = ID
458| END ELSE
459| PRINT EL:TERM.DRV:'Next item counter record not found!':TERM.NV:BEL:
460| INPUT ANS:
461| PRINT EL:
462| OK = FALSE
463| RETURN
464| END
465| END ELSE
466| *
467| *************************************************************************
468| *
469| * Try to read the customer record from the entered ID
470| *
471| GOSUB READ.RECORD
472| IF NOT(OK) THEN
473| *
474| ************************************************************************
475| *
476| * The attempt to read a record failed - assume the ID is a search string
477| *
478| GOSUB HIDEPIX ;* Pictures always show on top of text, so hide before call
479| CALL UIEX.GET.CUST.IDX(XID,ID,FN.CUST.XREF,FN.CUST)
480| GOSUB DSPSCRN ;* Refresh the screen after index lookup
481| *
482| ************************************************************************
483| *
484| * If the user did not select an item in the search routine, reprompt
485| *
486| IF XID EQ '' THEN RETURN
487| *
488| ************************************************************************
489| *
490| * Try to read the customer record from the selected ID
491| *
492| ID = XID
493| GOSUB READ.RECORD
494| *
495| END
496| END
497|END
498|*
499|**************************************************************************
500|*
501|* If success, display the new record, otherwise show warning message
502|*
503|IF OK THEN
504| GOSUB DSPDATA ;* Display new record
505|END ELSE
506| PRINT EL:TERM.DRV:'Please enter a valid customer ID!':TERM.NV:BEL:
507|END
508|RETURN
509|*
510|*
511|**************************************************************************
512|*
513|* The READ.RECORD subroutine reads a new customer record from the file and
514|* initializes the internal field data array (IDATA) from the record array
515|* (CUST.REC). If the record does not exist, the routine returns with the
516|* OK indicator variable set to FALSE. Otherwise OK is set to TRUE, and the
517|* CUST.ID variable is set to the new ID.
518|*
519|READ.RECORD: *
520|*
521|MATREAD CUST.REC FROM FN.CUST,ID THEN
522| CUST.ID = ID
523| IDATA(1) = CUST.ID
524| IDATA(2) = CUST.CONTACT
525| IDATA(3) = CUST.NAME
526| IDATA(4) = CUST.ADDRESS1
527| IDATA(5) = CUST.ADDRESS2
528| IDATA(6) = CUST.CITY
529| IDATA(7) = CUST.ST
530| IDATA(8) = CUST.ZIP
531| IDATA(9) = CUST.COUNTRY
532| IDATA(10) = CUST.PHONE
533| IDATA(11) = CUST.FAX
534| IDATA(12) = CUST.HISTORY
535| OK = TRUE ;* Set the SUCCESS indicator
536|END ELSE
537| OK = FALSE ;* Set the FAILURE indicator
538|END
539|RETURN
540|*
541|*
542|**************************************************************************
543|*
544|* The DELETE.RECORD subroutine confirms that the user intends to delete
545|* the current record. If the action is confirmed, the CUST.DELETE
546|* subroutine is called to perform the deletion. A separate subroutine is
547|* used to handle updating indexes, etc.
548|*
549|DELETE.RECORD: *
550|*
551|IF CUST.ID NE '' THEN
552| PRINT EL:TERM:DRV:'Are you sure you want to delete this customer? ':TERM:NV:
553| INPUT ANS:
554| IF ANS[1,1] EQ 'Y' OR ANS[1,1] EQ 'y' THEN
555| * Deletion has been confirmed - do the delete
556| OK = TRUE ;* Set the SUCCESS indicator
557| CALL UIEX.CUST.DELETE(CUST.ID,FN.CUST,FN.CUST.XREF)
558| DONE = TRUE ;* proceed to next record
559| GOSUB HIDEPIX ;* erase picture
560| END ELSE
561| OK = FALSE ;* Set the FAILURE indicator
562| END
563|END
564|RETURN
565|*
566|*
567|**************************************************************************
568|*
569|* The SAVE.RECORD subroutine copies internal field data from the IDATA
570|* array to the customer record array (CUST.REC). The CUST.UPDATE
571|* subroutine is called to perform the update. A separate subroutine is
572|* used to handle updating indexes, etc.
573|*
574|SAVE.RECORD: *
575|*
576|IF IDATA(1) NE '' THEN
577| * Copy data from the internal field data array (IDATA) to the CUST.REC array
578| CUST.ID = IDATA(1)
579| CUST.CONTACT = IDATA(2)
580| CUST.NAME = IDATA(3)
581| CUST.ADDRESS1 = IDATA(4)
582| CUST.ADDRESS2 = IDATA(5)
583| CUST.CITY = IDATA(6)
584| CUST.ST = IDATA(7)
585| CUST.ZIP = IDATA(8)
586| CUST.COUNTRY = IDATA(9)
587| CUST.PHONE = IDATA(10)
588| CUST.FAX = IDATA(11)
589| CUST.HISTORY = IDATA(12)
590| * Update the file
591| CALL UIEX.CUST.UPDATE(CUST.ID,MAT CUST.REC,FN.CUST,FN.CUST.XREF)
592| OK = TRUE ;* Set the SUCCESS indicator
593|END ELSE
594| OK = FALSE ;* Set the FAILURE indicator
595|END
596|DONE = TRUE ;* proceed to next record
597|RETURN
598|*
599|*
600|**************************************************************************
601|*
602|* The RESTART subroutine prepares the internal field data array (IDATA),
603|* customer record array (CUST.REC) and ID for a new customer record.
604|*
605|RESTART: *
606|*
607|CUST.ID = ''
608|MAT CUST.REC = ''
609|MAT IDATA = ''
610|RETURN
611|*
612|*
613|**************************************************************************
614|*
615|* The CHECK.CHANGED subroutine checks if any internal field data has been
616|* changed. The OK indicator variable is set to FALSE if any data is
617|* changed, otherwise it is set to TRUE. The ID field is not checked, since
618|* it is appropriately handled by the CHECK.ID subroutine.
619|*
620|CHECK.CHANGED: *
621|*
622|OK = FALSE
623|IF CUST.CONTACT # IDATA(2) THEN RETURN
624|IF CUST.NAME # IDATA(3) THEN RETURN
625|IF CUST.ADDRESS1 # IDATA(4) THEN RETURN
626|IF CUST.ADDRESS2 # IDATA(5) THEN RETURN
627|IF CUST.CITY # IDATA(6) THEN RETURN
628|