DIAUTL ;GFT/MSC - UTILITIES TO TURN ON AND TO ANALYZE FILEMAN AUDITS; May 01, 2023@08:35:32
 ;;22.2;VA FileMan;**27**;Jan 05, 2016;Build 7
 ;;Per VA Directive 6402, this routine should not be modified.
 ;;Submitted to OSEHRA 5 January 2015 by the VISTA Expertise Network.
 ;;Based on Medsphere Systems Corporation's MSC FileMan 1051.
 ;;Licensed under the terms of the Apache License, Version 2.0.
 ;GFT;**76,140,1000,1004,1005,1012,1022,1023,1039,1043,1044,1052,1053,1054**
 ;
TURNONDD(DIFILE,DIMODE) ;Turn on DATA DICTIONARY AUDITING  --THIS IS NOW A NO-OP, BECAUSE WE AUDIT ALL DD CHANGES IN FILE .6!!!!
 K:$G(DIMODE)=1 DIMODE S DIMODE=$G(DIMODE,"Y")
 I DIMODE'="Y",DIMODE'="N" D BLD^DIALOG(200) Q
 I DIFILE<1.11 Q
 I '$D(^DIC(+DIFILE)) D BLD^DIALOG(401,DIFILE) Q
 S ^DD(DIFILE,0,"DDA")=DIMODE ;It's really just one SET!
 Q
 ;
DISP(DDB) ;DISPLAY DD CHANGES FROM ^DDA SINCE DATE 'DDB'
 N DIA,FR,BY,TO,DHD,DDHD,DIC,L,POP,DDIO,DIOEND,DDTOUT,DIOSL,DIFIXPT,DIFIXPTH,DDIOST,DIOBEG S DIFIXPT=1 ;KEEPS ^%ZIS FROM BEING CALLED IN ^DIP3
 D ^%ZIS Q:POP  S DDIO=$G(ION,IO),DDIOST=IOST U IO
 F DIA=0:0 S DIA=$O(^DDA(DIA)) Q:'DIA  S FR=$O(^DDA(DIA,"D",DDB)) D:FR  Q:$D(DDTOUT)
 .U IO W @IOF D DDHD
 .S DIC="^DDA("_DIA_",",BY="-(#.03)@",TO=DT_.2359,FLDS="[DIAUTL]",L=0
 .S DIOEND="S:$G(DIOO1) DDTOUT=1",DIOSL=IOSL,DIOBEG="S ^UTILITY($J,1)=""D DISP1^DIAUTL""" ;DHD="W ?0 D DDHD^DIAUTL",IOP=DDIO
 .D EN1^DIP
 .I $G(DDIOST)?1"C".E N DIRUT,DIR S DIR(0)="E" D ^DIR W ! I $G(DIRUT) S DDTOUT=1
 U IO W @IOF D CLOSE^DIO4
 Q
DISP1 ;CALLED FROM ^UTILITY($J,1) TO HOLD LONG PRINTOUT FROM A SINGLE FILE'S DATA DICTIONARY AUDIT
 I $G(DDIOST)?1"C".E,DC?.N W $C(7) R Y:DTIME W:$Y # E  S DN=0,DDTOUT=1 Q
 S DC=1
 Q
 ;
DDHD S DDHD="DATA DICTIONARY CHANGES, "_$P($G(^DIC(DIA,0)),U)_" FILE(#"_DIA_")" S:DDB>2000000 DDHD=DDHD_" since "_$$DATE^DIUTL(DDB)
 W DDHD,!
 W "FIELD                     ATTRIBUTE                                USER NUMBER",!
 W "------------------------------------------------------------------------------",!
 Q
 ;
 ;
TURNON(DIFILE,FLDS,DIMODE,DICOND) ;Turn on AUDITING
 ;Input: DIFILE=file #, FLDS=fields to audit, DIMODE=mode("y","n","e"), DICOND=audit condition ;p27
 ;DICOND is only set if FLDS=single field
 N D,DIFIELD,DIE,DR,DA,DIQUIET,DIEZS,D0,DQ,DI,DIC,X
 K:$G(DIMODE)=1 DIMODE S DIMODE=$E($G(DIMODE,"y"))
 I DIMODE'="y",DIMODE'="e",DIMODE'="n" D BLD^DIALOG(200) Q
 S DIQUIET=1,DIEZS=1 Q:DIFILE<1.11&(DIFILE-.4)&(DIFILE-.401)&(DIFILE-.402)&(DIFILE-.403)&(DIFILE-.5)&(DIFILE-.7)&(DIFILE-.84)&(DIFILE-.847)
 D DT^DICRW
 I $G(DICOND)]"",FLDS=+FLDS,FLDS'=.001 D  Q  ;add Audit Condition to a field ;p27
 . S DIFIELD=FLDS D ON
 . Q:DIMODE="n"  ;quit if no audit
 . S DR="1.2////"_DICOND,DIE="^DD("_DIFILE_",",DA(1)=DIFILE,DA=DIFIELD
 . D ^DIE
 . Q
 F DIFIELD=0:0 S DIFIELD=$O(^DD(DIFILE,DIFIELD)) Q:'DIFIELD  D:$$FLDSINC(DIFILE,FLDS,DIFIELD) ON
 Q
ON N DIOLD
 S DIOLD=$G(^DD(DIFILE,DIFIELD,"AUDIT")) I DIOLD=DIMODE Q  ;It's already on
 S D=$P($G(^(0)),U,2) Q:D["C"
 I D D TURNON(+D,"**",DIMODE) Q  ;Recursive!
 S DR="1.1////"_DIMODE,DIE="^DD("_DIFILE_",",DA(1)=DIFILE,DA=DIFIELD
 I DA=.001,DIMODE="y" Q  ;CAN'T AUDIT NUMBER FIELD!!
 S:DIMODE="n" DR=DR_";1.2////@" ;delete Audit Condition if not auditing
 D ^DIE
 D IN^DIU0(DIFILE,DIFIELD),DDAUDIT(DIFILE,DIFIELD,1.1,DIOLD,DIMODE)
 I $G(^DD(DIFILE,0,"DIK"))]"" D EN2^DIKZ(DIFILE,"",^("DIK")) ;Recompile CROSS-REFS if auditing changes
 Q
 ;
CHANGED(FILE,FLDS,FLAGS,ARRAY,START,END) ;
 ;Returns in @ARRAY the list of entries in FILE who had any of the fields in FLDS changed from START to END
 ;If FLAGS is "O", the Oldest values are saved in @ARRAY@(entry,field)
 N GLO,E,F,T,D,%I
 K @ARRAY
 S FLAGS=$G(FLAGS)
 S GLO=^DIC(FILE,0,"GL")
 I '$G(START) S START=0
 I '$G(END) D NOW^%DTC S END=%
 S T=START D  F  S T=$O(^DIA(FILE,"C",T)) Q:T>END!'T  D
 .F D=0:0 S D=$O(^DIA(FILE,"C",T,D)) Q:'D  D
 ..S E=$G(^DIA(FILE,D,0)) Q:$P(E,U,6)="i"!'E
 ..I $D(@ARRAY@(+E)),FLAGS="" Q
 ..S F=+$P(E,U,3) Q:'$$FLDSINC(FILE,FLDS,F)
 ..I '$D(@(GLO_"+E)")),FLAGS="" Q
 ..S @ARRAY@(+E)="" I FLAGS["O",'$D(@ARRAY@(+E,F)) S @ARRAY@(+E,F)=$G(^DIA(FILE,D,2))
 Q
 ;
FIRST(DIQGR,ENTRY,FLDS) ;
 N LOF S LOF=1 G LOF
LAST(DIQGR,ENTRY,FLDS) ;returns DATE^USER who most recently touched any of the FLDS in ENTRY in File DIQGR
 N LOF S LOF=-1
LOF N E,F,DILAST,DENTRY,L
 S DILAST="",DENTRY=+ENTRY
 I ENTRY["," D
 .F F=2:1 Q:'$D(^DD(DIQGR,0,"UP"))  S DENTRY=$P(ENTRY,",",F)_","_DENTRY
 D E
 S DENTRY=ENTRY_","
 F  S DENTRY=$O(^DIA(DIQGR,"B",DENTRY)) Q:DENTRY-ENTRY  D E
 Q DILAST
 ;
E S E="" F  S E=$O(^DIA(DIQGR,"B",DENTRY,E),LOF) Q:'E  I $$FLDSINC(DIQGR,FLDS,+$P($G(^DIA(DIQGR,E,0)),U,3)) D  Q:DENTRY=ENTRY&DILAST
 .Q:$P(^DIA(DIQGR,E,0),U,6)="i"  ;Ignore INQUIRY
 .S L=$P(^(0),"^",2)_"^"_$P(^(0),"^",4)_"^"_$P($G(^(4.1)),U)
 .I LOF=-1,L>DILAST S DILAST=L
 .I LOF=1,DILAST>L!'DILAST S DILAST=L
 Q
 ;
DATE(FILE,FIELD) ;
 D VALUE(FILE,FIELD,2) Q
 ;
USER(FILE,FIELD) ;
 D VALUE(FILE,FIELD,4) Q
 ;
VALUE(FILE,FIELD,TU) ;FILE' can be SubFile
 N DIACMP,ENTRY,I
 S ENTRY=+$G(D0)
 F I=1:1 Q:'$D(^DD(FILE,0,"UP"))  S ENTRY=ENTRY_","_+$G(@("D"_I)),F=^("UP"),FIELD=$O(^DD(F,"SB",FILE,0))_","_FIELD,FILE=F
 D PRIOR(FILE,ENTRY,FIELD,.DIACMP)
 S D="" F  S D=$O(DIACMP(D),-1) Q:'D  S X=$S($G(TU):$P(^DIA(FILE,D,0),U,TU),1:DIACMP(D)) X DICMX Q:'$D(D)
 S X="" Q
 ;
PRIOR(FILE,ENTRY,FIELD,OUT) ;
 N E
 F E=0:0 S E=$O(^DIA(FILE,"B",ENTRY,E)) Q:'E  I $P($G(^DIA(FILE,E,0)),U,3)=FIELD S OUT(E)=$G(^(2))
 Q
 ;
FLDSINC(DIQGR,DR,DIAUTLF) ;is DIAUTLF within DR?  -- from 'DIQGQ' routine
 I DR=""!'DIAUTLF Q 0
 I DR="*" Q 1
 N DIAUGOT,DIQGCP,DIQGDD,DIQGXDC,DIQGXDF,DIQGXDI,DIQGXDN,DIQGXDD
 S DIQGXDC=0,DIAUGOT=0,DIQGDD=1,DIQGCP="D"
 I '$D(DIQGR) N X S X(1)="FILE" D 202 Q 0
 S DIQGXDD="^DD("_DIQGR_")"
 S:DIQGR DIQGR=$S(DIQGDD:$$DD(DIQGR),1:$$ROOT^DIQGU(DIQGR,.DA)) I DIQGR="" N X S X(1)="FILE AND IEN COMBINATION" D 202 Q 0
 F DIQGXDI=1:1 S DIQGXDF=$P(DR,";",DIQGXDI),DIQGXDN=$P(DIQGXDF,":") Q:DIQGXDF=""  D RANGE G GOT:DIAUGOT
NOGOT Q 0
 ;
RANGE I DIQGXDC,$P(^DD(+DIQGXDC,.01,0),"^",2)'["W" S:DR="**" DIQGXDN=DIQGXDN_"*" Q:$L(DIQGXDN,"*")'=2  ;multiple
 I DIQGXDN'?.N,$L(DIQGXDN,"*")=2,$P(DIQGXDN,"*")]"",$D(@DIQGXDD@("B",$P(DIQGXDN,"*"))) S DIQGXDN=$O(^($P(DIQGXDN,"*"),""))_"*"
 I DIQGXDN?1.2"*" S DIAUGOT=1 Q
 Q:DIAUTLF<DIQGXDN  I $P(DIQGXDF,":",2)<DIAUTLF Q:DIAUTLF-DIQGXDN
 S DIAUGOT=1 Q
 ;
GOT Q 1
 ;
DD(X) Q:'$D(^DD(X)) "" Q "^DD("_X_","
202 D BLD^DIALOG(202,.X) Q  ;bad parameter
 ;
 ;
GET(FIL,DA,DATE,TMP,FIELD) ;BUILD 'TMP' ARRAY AS OF DATE
 ;DA is in IEN format    FIELD, optional, means just look at one field
 K @TMP
 N DAT,FLD,FILE,F,D,E,B,C,T
 S F=FIL,FILE=$$FNO^DILIBF(F),@TMP=FILE,D=+$P(DA,",",$L(DA,",")-1) I 'D S D=DA
 I F=FILE F E=0:0 S E=$O(^DIA(FILE,"B",D,E)) Q:'E  D L G Q:$G(@TMP@(F,D_","))
SUBFILES S D=D_"," F  S E=D,D=$O(^DIA(FILE,"B",D)) Q:D-E  D
 .F E=0:0 S E=$O(^DIA(FILE,"B",D,E)) Q:'E  D L
 Q
L I $P($G(^DIA(FILE,E,0)),U)'=D Q
 S FLD=$P(^(0),U,3),DAT=$P(^(0),U,2),I="",F=FILE
 F  S C=$L(FLD,","),I=I_$P(D,",",C)_"," Q:C=1  S T=+FLD G Q:'$D(^DD(F,T,0)) S T=+$P(^(0),U,2) G Q:T'>F!'$D(^DD(T)) S F=T,FLD=$P(FLD,",",2,C)
 I FLD=.01,DAT>DATE,$P(^DIA(FILE,E,0),U,5)="A" K @TMP@(F,I) S @TMP@(F,I)=1 Q  ;THAT ENTRY OR SUB-ENTRY DIDN'T EXIST AS OF DATE  2nd level will only be defined in this case
 I $G(FIELD),FLD-FIELD!(F-FIL) Q
 I '$D(@TMP@(F,I,FLD)) S @TMP@(F,I,FLD)=DAT_U_E Q
 I DAT>DATE Q
 I @TMP@(F,I,FLD)<DAT S @TMP@(F,I,FLD)=DAT_U_E
Q Q
 ;
DIA(DAT,FILE,X,DIAUTLEX) ;FROM DIQG AND DIQGQ
 ;X is a node value from the 'TMP' array built by the GET subroutine, above
 ;DAT is the date/time as of which we want the audited value
 ;DIAUTLEX may contain "E" if we want external value
 I X>DAT Q $$D(2) ;We know what it was before deletion
 Q $$D(3)
D(ON) S X=$G(^DIA(FILE,+$P(X,U,2),ON)) I $G(DIAUTLEX)["E" Q X
 N S,Y S S=$G(^(ON+.1)) I X]"",S="" D  I Y>0 Q Y
 .N %DT S %DT="T" D ^%DT
 S S=$P(S,U) I S]"" Q S
 Q X
 ;
DDAUDIT(B0,DA,A0,A1,A2) ;B0=File or SubFile,  DA=Field, A0=Attribute #, A1=Old value, A2=New value
 N DDA,%,%T,%D,J,B3,I
 Q:'$D(DUZ)!'$G(DT)
 D IJ^DIUTL(B0)
 S A0=+$G(A0),A0=$P($G(^DD(0,A0,0)),U)_U_A0
 K:$G(A1)="" A1 L:$G(A2)="" A2
 D P^DICATTA Q
 
--- Routine Detail   --- with STRUCTURED ROUTINE LISTING ---[H[J[2J[HDIAUTL   8260     printed  Sep 23, 2025@20:21:06                                                                                                                                                                                                      Page 2
DIAUTL    ;GFT/MSC - UTILITIES TO TURN ON AND TO ANALYZE FILEMAN AUDITS; May 01, 2023@08:35:32
 +1       ;;22.2;VA FileMan;**27**;Jan 05, 2016;Build 7
 +2       ;;Per VA Directive 6402, this routine should not be modified.
 +3       ;;Submitted to OSEHRA 5 January 2015 by the VISTA Expertise Network.
 +4       ;;Based on Medsphere Systems Corporation's MSC FileMan 1051.
 +5       ;;Licensed under the terms of the Apache License, Version 2.0.
 +6       ;GFT;**76,140,1000,1004,1005,1012,1022,1023,1039,1043,1044,1052,1053,1054**
 +7       ;
TURNONDD(DIFILE,DIMODE) ;Turn on DATA DICTIONARY AUDITING  --THIS IS NOW A NO-OP, BECAUSE WE AUDIT ALL DD CHANGES IN FILE .6!!!!
 +1        if $GET(DIMODE)=1
               KILL DIMODE
           SET DIMODE=$GET(DIMODE,"Y")
 +2        IF DIMODE'="Y"
               IF DIMODE'="N"
                   DO BLD^DIALOG(200)
                   QUIT 
 +3        IF DIFILE<1.11
               QUIT 
 +4        IF '$DATA(^DIC(+DIFILE))
               DO BLD^DIALOG(401,DIFILE)
               QUIT 
 +5       ;It's really just one SET!
           SET ^DD(DIFILE,0,"DDA")=DIMODE
 +6        QUIT 
 +7       ;
DISP(DDB) ;DISPLAY DD CHANGES FROM ^DDA SINCE DATE 'DDB'
 +1       ;KEEPS ^%ZIS FROM BEING CALLED IN ^DIP3
           NEW DIA,FR,BY,TO,DHD,DDHD,DIC,L,POP,DDIO,DIOEND,DDTOUT,DIOSL,DIFIXPT,DIFIXPTH,DDIOST,DIOBEG
           SET DIFIXPT=1
 +2        DO ^%ZIS
           if POP
               QUIT 
           SET DDIO=$GET(ION,IO)
           SET DDIOST=IOST
           USE IO
 +3        FOR DIA=0:0
               SET DIA=$ORDER(^DDA(DIA))
               if 'DIA
                   QUIT 
               SET FR=$ORDER(^DDA(DIA,"D",DDB))
               if FR
                   Begin DoDot:1
 +4                    USE IO
                       WRITE @IOF
                       DO DDHD
 +5                    SET DIC="^DDA("_DIA_","
                       SET BY="-(#.03)@"
                       SET TO=DT_.2359
                       SET FLDS="[DIAUTL]"
                       SET L=0
 +6       ;DHD="W ?0 D DDHD^DIAUTL",IOP=DDIO
                       SET DIOEND="S:$G(DIOO1) DDTOUT=1"
                       SET DIOSL=IOSL
                       SET DIOBEG="S ^UTILITY($J,1)=""D DISP1^DIAUTL"""
 +7                    DO EN1^DIP
 +8                    IF $GET(DDIOST)?1"C".E
                           NEW DIRUT,DIR
                           SET DIR(0)="E"
                           DO ^DIR
                           WRITE !
                           IF $GET(DIRUT)
                               SET DDTOUT=1
                   End DoDot:1
               if $DATA(DDTOUT)
                   QUIT 
 +9        USE IO
           WRITE @IOF
           DO CLOSE^DIO4
 +10       QUIT 
DISP1     ;CALLED FROM ^UTILITY($J,1) TO HOLD LONG PRINTOUT FROM A SINGLE FILE'S DATA DICTIONARY AUDIT
 +1        IF $GET(DDIOST)?1"C".E
               IF DC?.N
                   WRITE $CHAR(7)
                   READ Y:DTIME
                   if $Y
                       WRITE #
                  IF '$TEST
                       SET DN=0
                       SET DDTOUT=1
                       QUIT 
 +2        SET DC=1
 +3        QUIT 
 +4       ;
DDHD       SET DDHD="DATA DICTIONARY CHANGES, "_$PIECE($GET(^DIC(DIA,0)),U)_" FILE(#"_DIA_")"
           if DDB>2000000
               SET DDHD=DDHD_" since "_$$DATE^DIUTL(DDB)
 +1        WRITE DDHD,!
 +2        WRITE "FIELD                     ATTRIBUTE                                USER NUMBER",!
 +3        WRITE "------------------------------------------------------------------------------",!
 +4        QUIT 
 +5       ;
 +6       ;
TURNON(DIFILE,FLDS,DIMODE,DICOND) ;Turn on AUDITING
 +1       ;Input: DIFILE=file #, FLDS=fields to audit, DIMODE=mode("y","n","e"), DICOND=audit condition ;p27
 +2       ;DICOND is only set if FLDS=single field
 +3        NEW D,DIFIELD,DIE,DR,DA,DIQUIET,DIEZS,D0,DQ,DI,DIC,X
 +4        if $GET(DIMODE)=1
               KILL DIMODE
           SET DIMODE=$EXTRACT($GET(DIMODE,"y"))
 +5        IF DIMODE'="y"
               IF DIMODE'="e"
                   IF DIMODE'="n"
                       DO BLD^DIALOG(200)
                       QUIT 
 +6        SET DIQUIET=1
           SET DIEZS=1
           if DIFILE<1.11&(DIFILE-.4)&(DIFILE-.401)&(DIFILE-.402)&(DIFILE-.403)&(DIFILE-.5)&(DIFILE-.7)&(DIFILE-.84)&(DIFILE-.847)
               QUIT 
 +7        DO DT^DICRW
 +8       ;add Audit Condition to a field ;p27
           IF $GET(DICOND)]""
               IF FLDS=+FLDS
                   IF FLDS'=.001
                       Begin DoDot:1
 +9                        SET DIFIELD=FLDS
                           DO ON
 +10      ;quit if no audit
                           if DIMODE="n"
                               QUIT 
 +11                       SET DR="1.2////"_DICOND
                           SET DIE="^DD("_DIFILE_","
                           SET DA(1)=DIFILE
                           SET DA=DIFIELD
 +12                       DO ^DIE
 +13                       QUIT 
                       End DoDot:1
                       QUIT 
 +14       FOR DIFIELD=0:0
               SET DIFIELD=$ORDER(^DD(DIFILE,DIFIELD))
               if 'DIFIELD
                   QUIT 
               if $$FLDSINC(DIFILE,FLDS,DIFIELD)
                   DO ON
 +15       QUIT 
ON         NEW DIOLD
 +1       ;It's already on
           SET DIOLD=$GET(^DD(DIFILE,DIFIELD,"AUDIT"))
           IF DIOLD=DIMODE
               QUIT 
 +2        SET D=$PIECE($GET(^(0)),U,2)
           if D["C"
               QUIT 
 +3       ;Recursive!
           IF D
               DO TURNON(+D,"**",DIMODE)
               QUIT 
 +4        SET DR="1.1////"_DIMODE
           SET DIE="^DD("_DIFILE_","
           SET DA(1)=DIFILE
           SET DA=DIFIELD
 +5       ;CAN'T AUDIT NUMBER FIELD!!
           IF DA=.001
               IF DIMODE="y"
                   QUIT 
 +6       ;delete Audit Condition if not auditing
           if DIMODE="n"
               SET DR=DR_";1.2////@"
 +7        DO ^DIE
 +8        DO IN^DIU0(DIFILE,DIFIELD)
           DO DDAUDIT(DIFILE,DIFIELD,1.1,DIOLD,DIMODE)
 +9       ;Recompile CROSS-REFS if auditing changes
           IF $GET(^DD(DIFILE,0,"DIK"))]""
               DO EN2^DIKZ(DIFILE,"",^("DIK"))
 +10       QUIT 
 +11      ;
CHANGED(FILE,FLDS,FLAGS,ARRAY,START,END) ;
 +1       ;Returns in @ARRAY the list of entries in FILE who had any of the fields in FLDS changed from START to END
 +2       ;If FLAGS is "O", the Oldest values are saved in @ARRAY@(entry,field)
 +3        NEW GLO,E,F,T,D,%I
 +4        KILL @ARRAY
 +5        SET FLAGS=$GET(FLAGS)
 +6        SET GLO=^DIC(FILE,0,"GL")
 +7        IF '$GET(START)
               SET START=0
 +8        IF '$GET(END)
               DO NOW^%DTC
               SET END=%
 +9        SET T=START
           Begin DoDot:1
 +10           FOR D=0:0
                   SET D=$ORDER(^DIA(FILE,"C",T,D))
                   if 'D
                       QUIT 
                   Begin DoDot:2
 +11                   SET E=$GET(^DIA(FILE,D,0))
                       if $PIECE(E,U,6)="i"!'E
                           QUIT 
 +12                   IF $DATA(@ARRAY@(+E))
                           IF FLAGS=""
                               QUIT 
 +13                   SET F=+$PIECE(E,U,3)
                       if '$$FLDSINC(FILE,FLDS,F)
                           QUIT 
 +14                   IF '$DATA(@(GLO_"+E)"))
                           IF FLAGS=""
                               QUIT 
 +15                   SET @ARRAY@(+E)=""
                       IF FLAGS["O"
                           IF '$DATA(@ARRAY@(+E,F))
                               SET @ARRAY@(+E,F)=$GET(^DIA(FILE,D,2))
                   End DoDot:2
           End DoDot:1
           FOR 
               SET T=$ORDER(^DIA(FILE,"C",T))
               if T>END!'T
                   QUIT 
               Begin DoDot:1
               End DoDot:1
 +16       QUIT 
 +17      ;
FIRST(DIQGR,ENTRY,FLDS) ;
 +1        NEW LOF
           SET LOF=1
           GOTO LOF
LAST(DIQGR,ENTRY,FLDS) ;returns DATE^USER who most recently touched any of the FLDS in ENTRY in File DIQGR
 +1        NEW LOF
           SET LOF=-1
LOF        NEW E,F,DILAST,DENTRY,L
 +1        SET DILAST=""
           SET DENTRY=+ENTRY
 +2        IF ENTRY[","
               Begin DoDot:1
 +3                FOR F=2:1
                       if '$DATA(^DD(DIQGR,0,"UP"))
                           QUIT 
                       SET DENTRY=$PIECE(ENTRY,",",F)_","_DENTRY
               End DoDot:1
 +4        DO E
 +5        SET DENTRY=ENTRY_","
 +6        FOR 
               SET DENTRY=$ORDER(^DIA(DIQGR,"B",DENTRY))
               if DENTRY-ENTRY
                   QUIT 
               DO E
 +7        QUIT DILAST
 +8       ;
E          SET E=""
           FOR 
               SET E=$ORDER(^DIA(DIQGR,"B",DENTRY,E),LOF)
               if 'E
                   QUIT 
               IF $$FLDSINC(DIQGR,FLDS,+$PIECE($GET(^DIA(DIQGR,E,0)),U,3))
                   Begin DoDot:1
 +1       ;Ignore INQUIRY
                       if $PIECE(^DIA(DIQGR,E,0),U,6)="i"
                           QUIT 
 +2                    SET L=$PIECE(^(0),"^",2)_"^"_$PIECE(^(0),"^",4)_"^"_$PIECE($GET(^(4.1)),U)
 +3                    IF LOF=-1
                           IF L>DILAST
                               SET DILAST=L
 +4                    IF LOF=1
                           IF DILAST>L!'DILAST
                               SET DILAST=L
                   End DoDot:1
                   if DENTRY=ENTRY&DILAST
                       QUIT 
 +5        QUIT 
 +6       ;
DATE(FILE,FIELD) ;
 +1        DO VALUE(FILE,FIELD,2)
           QUIT 
 +2       ;
USER(FILE,FIELD) ;
 +1        DO VALUE(FILE,FIELD,4)
           QUIT 
 +2       ;
VALUE(FILE,FIELD,TU) ;FILE' can be SubFile
 +1        NEW DIACMP,ENTRY,I
 +2        SET ENTRY=+$GET(D0)
 +3        FOR I=1:1
               if '$DATA(^DD(FILE,0,"UP"))
                   QUIT 
               SET ENTRY=ENTRY_","_+$GET(@("D"_I))
               SET F=^("UP")
               SET FIELD=$ORDER(^DD(F,"SB",FILE,0))_","_FIELD
               SET FILE=F
 +4        DO PRIOR(FILE,ENTRY,FIELD,.DIACMP)
 +5        SET D=""
           FOR 
               SET D=$ORDER(DIACMP(D),-1)
               if 'D
                   QUIT 
               SET X=$SELECT($GET(TU):$PIECE(^DIA(FILE,D,0),U,TU),1:DIACMP(D))
               XECUTE DICMX
               if '$DATA(D)
                   QUIT 
 +6        SET X=""
           QUIT 
 +7       ;
PRIOR(FILE,ENTRY,FIELD,OUT) ;
 +1        NEW E
 +2        FOR E=0:0
               SET E=$ORDER(^DIA(FILE,"B",ENTRY,E))
               if 'E
                   QUIT 
               IF $PIECE($GET(^DIA(FILE,E,0)),U,3)=FIELD
                   SET OUT(E)=$GET(^(2))
 +3        QUIT 
 +4       ;
FLDSINC(DIQGR,DR,DIAUTLF) ;is DIAUTLF within DR?  -- from 'DIQGQ' routine
 +1        IF DR=""!'DIAUTLF
               QUIT 0
 +2        IF DR="*"
               QUIT 1
 +3        NEW DIAUGOT,DIQGCP,DIQGDD,DIQGXDC,DIQGXDF,DIQGXDI,DIQGXDN,DIQGXDD
 +4        SET DIQGXDC=0
           SET DIAUGOT=0
           SET DIQGDD=1
           SET DIQGCP="D"
 +5        IF '$DATA(DIQGR)
               NEW X
               SET X(1)="FILE"
               DO 202
               QUIT 0
 +6        SET DIQGXDD="^DD("_DIQGR_")"
 +7        if DIQGR
               SET DIQGR=$SELECT(DIQGDD:$$DD(DIQGR),1:$$ROOT^DIQGU(DIQGR,.DA))
           IF DIQGR=""
               NEW X
               SET X(1)="FILE AND IEN COMBINATION"
               DO 202
               QUIT 0
 +8        FOR DIQGXDI=1:1
               SET DIQGXDF=$PIECE(DR,";",DIQGXDI)
               SET DIQGXDN=$PIECE(DIQGXDF,":")
               if DIQGXDF=""
                   QUIT 
               DO RANGE
               if DIAUGOT
                   GOTO GOT
NOGOT      QUIT 0
 +1       ;
RANGE     ;multiple
           IF DIQGXDC
               IF $PIECE(^DD(+DIQGXDC,.01,0),"^",2)'["W"
                   if DR="**"
                       SET DIQGXDN=DIQGXDN_"*"
                   if $LENGTH(DIQGXDN,"*")'=2
                       QUIT 
 +1        IF DIQGXDN'?.N
               IF $LENGTH(DIQGXDN,"*")=2
                   IF $PIECE(DIQGXDN,"*")]""
                       IF $DATA(@DIQGXDD@("B",$PIECE(DIQGXDN,"*")))
                           SET DIQGXDN=$ORDER(^($PIECE(DIQGXDN,"*"),""))_"*"
 +2        IF DIQGXDN?1.2"*"
               SET DIAUGOT=1
               QUIT 
 +3        if DIAUTLF<DIQGXDN
               QUIT 
           IF $PIECE(DIQGXDF,":",2)<DIAUTLF
               if DIAUTLF-DIQGXDN
                   QUIT 
 +4        SET DIAUGOT=1
           QUIT 
 +5       ;
GOT        QUIT 1
 +1       ;
DD(X)      if '$DATA(^DD(X))
               QUIT ""
           QUIT "^DD("_X_","
202       ;bad parameter
           DO BLD^DIALOG(202,.X)
           QUIT 
 +1       ;
 +2       ;
GET(FIL,DA,DATE,TMP,FIELD) ;BUILD 'TMP' ARRAY AS OF DATE
 +1       ;DA is in IEN format    FIELD, optional, means just look at one field
 +2        KILL @TMP
 +3        NEW DAT,FLD,FILE,F,D,E,B,C,T
 +4        SET F=FIL
           SET FILE=$$FNO^DILIBF(F)
           SET @TMP=FILE
           SET D=+$PIECE(DA,",",$LENGTH(DA,",")-1)
           IF 'D
               SET D=DA
 +5        IF F=FILE
               FOR E=0:0
                   SET E=$ORDER(^DIA(FILE,"B",D,E))
                   if 'E
                       QUIT 
                   DO L
                   if $GET(@TMP@(F,D_","))
                       GOTO Q
SUBFILES   SET D=D_","
           FOR 
               SET E=D
               SET D=$ORDER(^DIA(FILE,"B",D))
               if D-E
                   QUIT 
               Begin DoDot:1
 +1                FOR E=0:0
                       SET E=$ORDER(^DIA(FILE,"B",D,E))
                       if 'E
                           QUIT 
                       DO L
               End DoDot:1
 +2        QUIT 
L          IF $PIECE($GET(^DIA(FILE,E,0)),U)'=D
               QUIT 
 +1        SET FLD=$PIECE(^(0),U,3)
           SET DAT=$PIECE(^(0),U,2)
           SET I=""
           SET F=FILE
 +2        FOR 
               SET C=$LENGTH(FLD,",")
               SET I=I_$PIECE(D,",",C)_","
               if C=1
                   QUIT 
               SET T=+FLD
               if '$DATA(^DD(F,T,0))
                   GOTO Q
               SET T=+$PIECE(^(0),U,2)
               if T'>F!'$DATA(^DD(T))
                   GOTO Q
               SET F=T
               SET FLD=$PIECE(FLD,",",2,C)
 +3       ;THAT ENTRY OR SUB-ENTRY DIDN'T EXIST AS OF DATE  2nd level will only be defined in this case
           IF FLD=.01
               IF DAT>DATE
                   IF $PIECE(^DIA(FILE,E,0),U,5)="A"
                       KILL @TMP@(F,I)
                       SET @TMP@(F,I)=1
                       QUIT 
 +4        IF $GET(FIELD)
               IF FLD-FIELD!(F-FIL)
                   QUIT 
 +5        IF '$DATA(@TMP@(F,I,FLD))
               SET @TMP@(F,I,FLD)=DAT_U_E
               QUIT 
 +6        IF DAT>DATE
               QUIT 
 +7        IF @TMP@(F,I,FLD)<DAT
               SET @TMP@(F,I,FLD)=DAT_U_E
Q          QUIT 
 +1       ;
DIA(DAT,FILE,X,DIAUTLEX) ;FROM DIQG AND DIQGQ
 +1       ;X is a node value from the 'TMP' array built by the GET subroutine, above
 +2       ;DAT is the date/time as of which we want the audited value
 +3       ;DIAUTLEX may contain "E" if we want external value
 +4       ;We know what it was before deletion
           IF X>DAT
               QUIT $$D(2)
 +5        QUIT $$D(3)
D(ON)      SET X=$GET(^DIA(FILE,+$PIECE(X,U,2),ON))
           IF $GET(DIAUTLEX)["E"
               QUIT X
 +1        NEW S,Y
           SET S=$GET(^(ON+.1))
           IF X]""
               IF S=""
                   Begin DoDot:1
 +2                    NEW %DT
                       SET %DT="T"
                       DO ^%DT
                   End DoDot:1
                   IF Y>0
                       QUIT Y
 +3        SET S=$PIECE(S,U)
           IF S]""
               QUIT S
 +4        QUIT X
 +5       ;
DDAUDIT(B0,DA,A0,A1,A2) ;B0=File or SubFile,  DA=Field, A0=Attribute #, A1=Old value, A2=New value
 +1        NEW DDA,%,%T,%D,J,B3,I
 +2        if '$DATA(DUZ)!'$GET(DT)
               QUIT 
 +3        DO IJ^DIUTL(B0)
 +4        SET A0=+$GET(A0)
           SET A0=$PIECE($GET(^DD(0,A0,0)),U)_U_A0
 +5        if $GET(A1)=""
               KILL A1
           if $GET(A2)=""
               LOCK A2
 +6        DO P^DICATTA
           QUIT