postdata.sas
Go to the documentation of this file.
1 /**
2  @file postdata.sas
3  @brief Either returns the file diffs or actually loads the data to target
4  @details Before loading the target, a check is made against the time the
5  target was last updated (backend) and the time the DIFF was generated
6  (frontend). If the target was updated whilst the DIFF was on the screen,
7  then the provided diff may have been incorrect and so a new DIFF should be
8  generated and approved before load.
9 
10  Only 100 rows (of each DIFF type) are displayed on the DIFF screen.
11 
12  <h4> Service Inputs </h4>
13 
14  <h5> SASCONTROLTABLE </h5>
15  |ACTION:$char10.|TABLE:$char32.|DIFFTIME:$char29.|
16  |---|---|---|
17  |SHOW_DIFFS|DC20220208T142124517_124703_1184|"Tue, 08 Feb 2022 14:23:05 GMT"|
18 
19  <h4> SAS Macros </h4>
20  @li bitemporal_dataloader.sas
21  @li dc_assignlib.sas
22  @li mf_existds.sas
23  @li mf_existvar.sas
24  @li mf_getattrn.sas
25  @li mf_getengine.sas
26  @li mf_getquotedstr.sas
27  @li mf_getuniquelibref.sas
28  @li mf_getuser.sas
29  @li mf_getvarlist.sas
30  @li mf_nobs.sas
31  @li mf_verifymacvars.sas
32  @li mp_abort.sas
33  @li mp_cntlout.sas
34  @li mp_lockanytable.sas
35  @li mpe_accesscheck.sas
36  @li mpe_alerts.sas
37  @li mpe_runhook.sas
38  @li mpe_targetloader.sas
39  @li removecolsfromwork.sas
40 
41 
42  @version 9.2
43  @author 4GL Apps Ltd
44  @copyright 4GL Apps Ltd. This code may only be used within Data Controller
45  and may not be re-distributed or re-sold without the express permission of
46  4GL Apps Ltd.
47 
48 **/
49 
50 /* this could be a config setting if required */
51 %let maxdiff=100;
52 
53 %mpeinit()
54 
55 /* load parameters */
56 data _null_;
57  set work.sascontroltable;
58  call symputx('ACTION',ACTION);
59  call symputx('LOAD_REF',TABLE);
60  /* DIFFTIME is when the DIFF was generated on the frontend */
61  call symputx('DIFFTIME',DIFFTIME);
62  putlog (_all_)(=);
63 run;
64 
65 %global action is_err err_msg msg;
66 %let is_err=0;
67 
68 %let user=%mf_getuser();
69 %let sastime=%sysfunc(datetime());
70 data sastime;
71  dt_sastime=&sastime;
72 run;
73 
74 PROC FORMAT;
75  picture yymmddhhmmss other='%0Y-%0m-%0d %0H:%0M:%0S' (datatype=datetime);
76  picture flatdate other='%0Y%0m%0d_%0H%0M%0S' (datatype=datetime);
77 RUN;
78 
79 /* SHOW_DIFFS works by getting the temp tables from the bitemporal loader */
80 /* so we share much of the logic from the actual load process */
81 %let isfmtcat=0;
82 data APPROVE1;
83  set &mpelib..mpe_submit;
84  where TABLE_ID="&LOAD_REF";
85  /* fetch mpe_submit data */
86  libds=cats(base_lib,'.',base_ds);
87  REVIEWED_ON=put(reviewed_on_dttm,datetime19.);
88  call symputx('REVIEW_STATUS_ID',submit_status_cd,'l');
89  call symputx('NUM_OF_APPROVALS_REQUIRED',NUM_OF_APPROVALS_REQUIRED);
90  call symputx('num_of_approvals_remaining',num_of_approvals_remaining);
91 
92  /* other stuff that's useful to do in data step */
93  call symputx('orig_libds',libds);
94  call symputx('libds',libds);
95  if substr(cats(reverse(libds)),1,3)=:'CF-' then do;
96  libds=scan(libds,1,'-');
97  putlog "Format Catalog Captured";
98  call symputx('isfmtcat',1);
99  libds='work.fmtextract';
100  call symputx('libds',libds);
101  end;
102  putlog (_all_)(=);
103  /* convert provided string DIFFTIME back to a numeric SAS datetime */
104  if "&action" ne "SHOW_DIFFS" then do;
105  call symputx('DIFFTIME',input(symget('DIFFTIME'),anydtdtm18.));
106  end;
107  length difftime $32;
108  DIFFTIME=put(&sastime,datetime19.2);
109 run;
110 
111 %mp_cntlout(
112  iftrue=(&isfmtcat=1)
113  ,libcat=&orig_libds
114  ,fmtlist=0
115  ,cntlout=work.fmtextract
116 )
117 
118 %mp_abort(
119  iftrue=(%mf_verifymacvars(difftime orig_libds libds load_ref)=0)
120  ,mac=&_program
121  ,msg=%str(Missing: difftime orig_libds libds load_ref)
122 )
123 
124 /* security checks */
125 %mpe_accesscheck(&orig_libds,outds=authEDIT,user=&user,access_level=EDIT)
126 %mpe_accesscheck(&orig_libds,outds=authAPP,user=&user,access_level=APPROVE)
127 
128 %mp_abort(iftrue= (&syscc ne 0)
129  ,mac=&_program
130  ,msg=%str(syscc=&syscc Before entering postdata macro)
131 )
132 
133 %mp_abort(
134  iftrue=(
135  %mf_getattrn(work.authEDIT,NLOBS)=0 & %mf_getattrn(work.authAPP,NLOBS)=0
136  )
137  ,mac=&_program
138  ,msg=%str(&user not authorised to view approval screen for &orig_libds)
139 )
140 
141 %macro quickmacro(inds,outds);
142  data &outds ;
143  %if %length(&VAR_BUSFROM)>0 %then %do;
144  format &VAR_BUSFROM &VAR_BUSTO yymmddhhmmss.;
145  %end;
146  if 0 then set &emptybasetable;
147  set &inds;
148  %if %mf_existvar(&libds,&var_txfrom) %then %do;
149  drop &var_txfrom &var_txto;
150  %end;
151  %if %mf_existvar(&inds,_____DELETE__THIS__RECORD_____) %then %do;
152  drop _____DELETE__THIS__RECORD_____;
153  %end;
154  %if %mf_existvar(&inds,&VAR_PROCESSED) %then %do;
155  drop &VAR_PROCESSED;
156  %end;
157  run;
158 %mend quickmacro;
159 
160 %macro postdata();
161 
162 %if %quote(&REVIEW_STATUS_ID)=%quote(REJECTED)
163  or %quote(&REVIEW_STATUS_ID)=%quote(APPROVED) %then
164 %do;
165  data params; set approve1; run;
166  %webout(OPEN)
167  %webout(OBJ,PARAMS)
168  %webout(CLOSE)
169  %return;
170 %end;
171 
172 %mp_abort(iftrue= (&syscc ne 0)
173  ,mac=&_program..sas
174  ,msg=%str(syscc=&syscc)
175 )
176 
177 %if &action=APPROVE_TABLE %then %do;
178 
179  /* check user is authorised to approve table */
180  /* user could be an editor but not an approver */
181  %mp_abort(iftrue= (%mf_getattrn(work.authAPP,NLOBS)=0)
182  ,mac=&_program
183  ,msg=%str(&user may not APPROVE changes)
184  )
185 
186  /* see if this user has already submitted an approval */
187  %let prev_upload_check=1;
188  proc sql;
189  select count(*) into: prev_upload_check from &mpelib..mpe_review
190  where TABLE_ID="&LOAD_REF" and REVIEWED_BY_NM="&user"
191  and REVIEW_STATUS_ID ne "SUBMITTED";
192  %let authcheck=%mf_getattrn(work.authAPP,NLOBS);
193  %if &authcheck=0 or &prev_upload_check=1 %then %do;
194  %put %str(WARN)ING: &=authcheck &=prev_upload_check;
195  data apPARAMS;
196  AUTHORISED=&authcheck;
197  PREV_UPLOAD_CHECK=&prev_upload_check;
198  run;
199  %webout(OPEN)
200  %webout(OBJ,apPARAMS);
201  %webout(CLOSE)
202  %return;
203  %end;
204 
205  /* now check if table has been updated since DIFF screen shown */
206  %local fmt_tm usernm last_load etlsource;
207  %let last_load=0;
208  proc sql noprint;
209  select max(processed_dttm) format=16.2 into: last_load
210  from &mpelib..mpe_dataloads
211  where libref="%scan(&orig_libds,1,.)" and dsn="%scan(&orig_libds,2,.)";
212  select processed_dttm format=datetime19., user_nm, etlsource
213  into: fmt_tm, :usernm, :etlsource
214  from &mpelib..mpe_dataloads
215  where libref="%scan(&orig_libds,1,.)" and dsn="%scan(&orig_libds,2,.)"
216  and processed_dttm=&last_load;
217  %put TIMECHECK: &last_load>&difftime;
218  %if %sysevalf(&last_load>&difftime,boolean)=1 %then %do;
219  %let is_err=1;
220  %let err_msg=&orig_libds was updated in batch %trim(&etlsource
221  ) by %trim(&usernm) on &fmt_tm - please refresh the page!!;
222  %return;
223  %end;
224  %if &syscc ne 0 %then %do;
225  %let is_err=1;
226  %let err_msg=syscc=&syscc before logchange;
227  %return;
228  %end;
229 
230  /* upload about to commence so ensure logs */
231  options notes mprint source2;
232  %local oldloc;
233  %if %symexist(SYSPRINTTOLOG) %then %let oldloc=&SYSPRINTTOLOG;
234  %else %let oldloc=%qsysfunc(getoption(LOG));
235  %if %length(&oldloc)>0 %then %do;
236  proc printto
237  log="&mpelocapprovals/&LOAD_REF/approval.log";
238  run;
239  data _null_;
240  if _n_=1 then do;
241  length oldloc $1000;
242  oldloc=symget('oldloc');
243  putlog "****** redirected:" oldloc " *****";
244  end;
245  infile &oldloc;
246  input; putlog _infile_;
247  run;
248  %end;
249  %else %do;
250  proc printto
251  log="&mpelocapprovals/&LOAD_REF/approval.log";
252  run;
253  %end;
254 
255  %if &syscc ne 0 %then %do;
256  %let is_err=1;
257  %let err_msg=syscc=&syscc after logchange;
258  %return;
259  %end;
260 %end;
261 
262 /**
263  * upload the actual table
264  */
265 %local libref ds;
266 %let libref=%scan(&orig_libds,1,.);
267 %let ds=%scan(&orig_libds,2,.);
268 
269 proc sql noprint;
270 select PRE_APPROVE_HOOK, POST_APPROVE_HOOK, LOADTYPE, var_txfrom, var_txto
271  ,BUSKEY, VAR_BUSFROM, VAR_BUSTO
272  ,AUDIT_LIBDS, NOTES, coalesce(NUM_OF_APPROVALS_REQUIRED,1)
273  ,VAR_PROCESSED
274  into: PRE_APPROVE_HOOK, :POST_APPROVE_HOOK, :LOADTYPE,:var_txfrom,:var_txto
275  ,:BUSKEY,:VAR_BUSFROM,:VAR_BUSTO
276  ,:AUDIT_LIBDS, :TABLE_DESC, :NUM_OF_APPROVALS_REQUIRED_TOT
277  ,:VAR_PROCESSED
278  from &mpelib..mpe_tables
279  where &dc_dttmtfmt. lt tx_to
280  and libref="&libref"
281  and dsn="&ds";
282 
283 %mp_abort(
284  iftrue=(%mf_verifymacvars(mpelocapprovals orig_libds)=0)
285  ,mac=&_program
286  ,msg=%str(Missing: mpelocapprovals orig_libds)
287 )
288 
289 /* get dataset from approvals location (has same name as load_ref) */
290 %let tmplib=%mf_getuniquelibref();
291 libname &tmplib "&mpelocapprovals/&LOAD_REF";
292 data STAGING_DS;
293  set &tmplib..&LOAD_REF;
294 run;
295 
296 %mp_abort(iftrue= (&syscc ne 0)
297  ,mac=&_program..sas
298  ,msg=%str(syscc=&syscc before preapprove)
299 )
300 
301 %dc_assignlib(WRITE,&libref)
302 
303 /* run pre-approve hook - occurs both BEFORE _and_ AFTER the diff */
304 %mpe_runhook(PRE_APPROVE_HOOK)
305 
306 %mp_abort(iftrue= (&syscc ne 0)
307  ,mac=&_program..sas
308  ,msg=%str(syscc=&syscc after preapprove)
309 )
310 
311 %if &num_of_approvals_remaining>1 and &action=APPROVE_TABLE %then %do;
312 
313  /* append to mpe_review table */
314  %let apprno=%eval(&num_of_approvals_required-&num_of_approvals_remaining+1);
315  data work.append_review;
316  if 0 then set &mpelib..mpe_review;
317  TABLE_ID="&LOAD_REF";
318  BASE_TABLE="&orig_libds";
319  REVIEW_STATUS_ID="APPROVED";
320  REVIEWED_BY_NM="&user";
321  REVIEWED_ON_DTTM=&sastime;
322  REVIEW_REASON_TXT="APPROVAL &apprno of &num_of_approvals_required";
323  output;
324  stop;
325  run;
326  %mp_lockanytable(LOCK,
327  lib=&mpelib,ds=mpe_review,ref=%str(&LOAD_REF Approval),
328  ctl_ds=&mpelib..mpe_lockanytable
329  )
330  proc append base=&mpelib..mpe_review data=work.append_review;
331  run;
332  %mp_lockanytable(UNLOCK,
333  lib=&mpelib,ds=mpe_review,
334  ctl_ds=&mpelib..mpe_lockanytable
335  )
336 
337  /* update mpe_submit table */
338  %mp_lockanytable(LOCK,
339  lib=&mpelib,ds=mpe_submit,ref=%str(&LOAD_REF Approval),
340  ctl_ds=&mpelib..mpe_lockanytable
341  )
342  proc sql;
343  update &mpelib..mpe_submit
344  set num_of_approvals_remaining=&num_of_approvals_remaining-1,
345  reviewed_by_nm="&user",
346  reviewed_on_dttm=&sastime
347  where table_id="&LOAD_REF";
348  %mp_lockanytable(UNLOCK,
349  lib=&mpelib,ds=mpe_submit,
350  ctl_ds=&mpelib..mpe_lockanytable
351  )
352 
353  data apReqd;
354  AUTHORISED=1;
355  ALREADY_UPDATED=0;
356  ALREADY_UPDATED_DTTM=.;
357  set approve1; /* js will test for NUM_OF_APPROVALS_REQUIRED */
358  run;
359  %removecolsfromwork(___TMP___MD5)
360  %webout(OPEN)
361  %webout(OBJ,apReqd);
362  %webout(CLOSE)
363  %return;
364 
365 %end;
366 
367 %mp_abort(iftrue= (&syscc ne 0)
368  ,mac=&_program..sas
369  ,msg=%str(syscc=&syscc entering TARGETLOADER)
370 )
371 %mpe_targetloader(libds=&orig_libds
372  ,now= &sastime
373  ,etlsource=&LOAD_REF
374  ,STAGING_DS=STAGING_DS
375  ,dclib=&mpelib
376 %if &action=APPROVE_TABLE %then %do;
377  ,LOADTARGET=YES
378 %end;
379 %else %do;
380  ,LOADTARGET=NO
381 %end;
382  ,dc_dttmtfmt=&dc_dttmtfmt.
383 )
384 
385 
386 %if %mf_getattrn(STAGING_DS,NLOBS)=0 %then %do;
387  /* empty dataset! */
388  data out;
389  set STAGING_DS;
390  run;
391  %return;
392 %end;
393 
394 %mp_abort(iftrue= (&syscc ne 0)
395  ,mac=&_program..sas
396  ,msg=%str(syscc=&syscc entering SHOWDIFFS)
397 )
398 %if &action=SHOW_DIFFS %then %do;
399 
400  /**
401  * Now prepare the SHOW DIFFS (approve) screen
402  */
403 
404  /*To create the CURRENT diffs, we compare with the ACTUAL data. But first
405  need to find out what version TIME to query it for.. */
406  proc sql noprint;
407  select max(processed_dttm)-1 format=datetime19. into: tstamp
408  from &mpelib..mpe_dataloads
409  where libref="&libref" and dsn="&ds" and ETLSOURCE="&LOAD_REF";
410  quit;
411  %if &tstamp=. %then %let tstamp=%sysfunc(datetime(),datetime19.);
412 
413  /**
414  * now create the DIFFS dataset
415  * If using a database, then utilise pass through!
416  * Create a temporary table inside the database for joins..
417  */
418  options mprint;
419  %let engine_type=%mf_getEngine(%scan(&libds,1,.));
420  %put &libds engine type = &engine_type;
421  %local inner_table ;
422  %if &engine_type=OLEDB %then %do;
423  /* generate a unique ID for the temporary table */
424  data _null_;
425  call symputx('UNIQUE_REF'
426  ,cats(round(datetime(),1)
427  ,'_'
428  ,round(ranuni(0)*100000,1)
429  )
430  ,'l'
431  );
432  run;
433  %let inner_table=&libref.."##DIFF_&UNIQUE_REF"n;
434  proc sql;
435  create table &inner_table as
436  select * from work.outds_mod;
437  %end;
438  %else %let inner_table=work.outds_mod;
439  proc sql;
440  create view work.originals2 as
441  select b.*
442  from &inner_table a
443  inner join &libds
444  %if &loadtype=BITEMPORAL or &loadtype=TXTEMPORAL %then %do;
445  (where=("&tstamp"dt < &VAR_TXTO))
446  %end;
447  b
448  on 1
449  %do idx_pk=1 %to %sysfunc(countw(&buskey));
450  %let idx_val=%scan(&buskey,&idx_pk);
451  and a.&idx_val=b.&idx_val
452  %end;
453  order by %mf_getquotedstr(in_str=&buskey,dlm=%str(,),quote=)
454  ;
455 
456  create view bitemp5c_updates2 as
457  select * from work.outds_mod
458  order by %mf_getquotedstr(in_str=&buskey,dlm=%str(,),quote=)
459  ;
460 
461  data; set &libds;stop;run;
462  %let emptybasetable=&syslast;
463 
464  options varlenchk=nowarn; /* for small numerics (<8) */
465  %quickmacro(work.outds_del,deleted)
466  %quickmacro(work.outds_add,new)
467  %quickmacro(bitemp5c_updates2,updates)
468  %quickmacro(originals2,originals)
469 
470  %mp_abort(iftrue= (&syscc ne 0)
471  ,mac=&_program..sas
472  ,msg=%str(syscc=&syscc in quickmacro)
473  )
474 
475  /* extract colnames for md5 creation / change tracking */
476  proc contents noprint data=work.updates
477  out=cols (keep=name type length varnum format);
478  run;
479  proc sort data=cols out=cols(drop=varnum); by varnum;run;
480  data cols; set cols; name=upcase(name);run;
481  %let tempDIFFS_CSV=tempDiffs_%trim(
482  %sysfunc(datetime(),flatdate.)).csv;
483 
484  /**
485  * Store temp tables so we have a record of diffs
486  * do not change this libname or table name as it is used in some
487  * post approve hooks
488  */
489 
490  data TEMPDIFFS (compress=no) /* for realistic file size */;
491  length _____status $10;
492  set work.deleted (in=_____del)
493  work.new(in=_____new)
494  work.updates (in=_____upd)
495  work.originals2 (in=_____orig);
496  if _____del then _____status='DELETED ';
497  else if _____new then _____status='NEW';
498  else if _____upd then _____status='UPDATED';
499  else if _____orig then _____status='ORIGINAL';
500  run;
501  proc export data=TEMPDIFFS dbms=csv replace
502  outfile="&mpelocapprovals/&LOAD_REF/&tempDIFFS_CSV" ;
503  run;
504  proc sql noprint;
505  select filesize format=sizekmg10.1, filesize as filesize_raw
506  into: filesize,:filesize_raw
507  from dictionary.tables
508  where libname='WORK' and memtype='DATA' and memname='TEMPDIFFS';
509 
510  data params;
511  set approve1;
512  DIFFS_CSV="&tempDIFFS_CSV";
513  FILESIZE="&filesize";
514  FILESIZE_RAW=&filesize_raw;
515  if %mf_nobs(work.originals)>&maxdiff
516  or %mf_nobs(work.new)>&maxdiff
517  or %mf_nobs(work.deleted)>&maxdiff
518  or %mf_nobs(work.updates)>&maxdiff
519  then TRUNCATED="YES";
520  else TRUNCATED="NO";
521 
522  NUM_ADDED=%mf_getattrn(work.new,NLOBS);
523  NUM_DELETED=%mf_getattrn(work.deleted,NLOBS);
524  NUM_UPDATED=%mf_getattrn(work.updates,NLOBS);
525  SUBMITTED_ON=put(submitted_on_dttm,datetime19.);
526  %if %mf_getattrn(work.authAPP,NLOBS)>0 %then %do;
527  ISAPPROVER='YES';
528  %end;
529  %else %do;
530  ISAPPROVER='NO';
531  %end;
532  run;
533 
534  /*
535  * The PRE_APPROVE_HOOK may have applied custom formats to the staged table.
536  * To ensure consistency in the DIFF screen, we should apply the same formats
537  * to the base table. Limit rows at the same time.
538  */
539  data work.originals;
540  if 0 then set deleted new updates;
541  set work.originals;
542  if _n_>&maxdiff then stop;
543  run;
544 
545  /* get additional submits against the same base table */
546  proc sort data=&mpelib..mpe_submit(where=(
547  submit_status_cd='SUBMITTED'
548  and cats(base_lib,'.',base_ds)="&orig_libds"
549  and table_id ne "&LOAD_REF"
550  )) out=submits;
551  by descending submitted_on_dttm;
552  run;
553 
554  /* filter last 10 */
555  data submits;
556  set submits;
557  if _n_>10 then stop;
558  run;
559 
560  %mp_abort(iftrue= (&syscc ne 0)
561  ,mac=&_program..sas
562  ,msg=%str(syscc=&syscc SHOWDIFFS prior to streamout)
563  )
564 
565  %removecolsfromwork(___TMP___MD5)
566  %webout(OPEN)
567  %webout(OBJ,params)
568  %webout(OBJ,cols)
569  %webout(OBJ,submits)
570  %webout(OBJ,deleted,fmt=N,missing=STRING,maxobs=&maxdiff)
571  %webout(OBJ,new,fmt=N,missing=STRING,maxobs=&maxdiff)
572  %webout(OBJ,updates,fmt=N,missing=STRING,maxobs=&maxdiff)
573  %webout(OBJ,ORIGINALS,fmt=N,missing=STRING)
574  /* need same for formatted view */
575  %webout(OBJ,deleted,dslabel=fmt_deleted,fmt=Y,missing=STRING,maxobs=&maxdiff)
576  %webout(OBJ,new,dslabel=fmt_new,fmt=Y,missing=STRING,maxobs=&maxdiff)
577  %webout(OBJ,updates,dslabel=fmt_updates,fmt=Y,missing=STRING,maxobs=&maxdiff)
578  %webout(OBJ,originals,dslabel=fmt_ORIGINALS,fmt=Y,missing=STRING)
579  %webout(CLOSE)
580 
581  %if &engine_type=OLEDB %then %do;
582  proc sql; /* needs to be dropped AFTER view execution */
583  drop table &inner_table;
584  %end;
585 
586  %return;
587 %end;
588 
589 
590 %if &action=APPROVE_TABLE %then %do;
591  %approve:
592  /**
593  * store temp tables so we have a record of diffs
594  * do not change this libname or table name as it is used in some
595  * post approve hooks
596  * for REPLACE loads, temp tables not made, so make them
597  */
598  %if &LOADTYPE=REPLACE %then %do;
599  data work.outds_add; run;
600  data work.outds_mod; run;
601  data work.outds_del; run;
602  %end;
603  libname approve "&mpelocapprovals/&LOAD_REF";
604  data; set &libds;stop;run;
605  %let emptybasetable=&syslast;
606  data approve.ActualDiffs;
607  length _____STATUS_____ $10;
608  if 0 then set &emptybasetable;
609  set work.outds_del (in=_____del)
610  work.outds_add (in=_____new)
611  work.outds_mod (in=_____upd);
612  if _____del then _____STATUS_____='DELETED';
613  else if _____new then _____STATUS_____='NEW';
614  else if _____upd then _____STATUS_____='UPDATED';
615 
616  %if %mf_existvar(&libds,&var_txfrom) %then %do;
617  drop &var_txfrom &var_txto;
618  %end;
619  %if %mf_existvar(&libds,&VAR_PROCESSED) %then %do;
620  drop &VAR_PROCESSED;
621  %end;
622  run;
623 
624  proc export data=approve.ActualDiffs
625  outfile="&mpelocapprovals/&LOAD_REF/ActualDiffs.csv"
626  dbms=csv
627  replace;
628  run;
629 
630  /* update the control table to show table as approved */
631  /* append to mpe_review table */
632  %let apprno=%eval(&num_of_approvals_required-&num_of_approvals_remaining+1);
633  data work.append_review;
634  if 0 then set &mpelib..mpe_review;
635  TABLE_ID="&LOAD_REF";
636  BASE_TABLE="&orig_libds";
637  REVIEW_STATUS_ID="APPROVED";
638  REVIEWED_BY_NM="&user";
639  REVIEWED_ON_DTTM=&sastime;
640  REVIEW_REASON_TXT="APPROVAL &apprno of &num_of_approvals_required";
641  output;
642  stop;
643  run;
644  %mp_lockanytable(LOCK,
645  lib=&mpelib,ds=mpe_review,ref=%str(&LOAD_REF Approval),
646  ctl_ds=&mpelib..mpe_lockanytable
647  )
648  proc append base=&mpelib..mpe_review data=work.append_review;
649  run;
650  %mp_lockanytable(UNLOCK,
651  lib=&mpelib,ds=mpe_review,
652  ctl_ds=&mpelib..mpe_lockanytable
653  )
654 
655  /* update mpe_submit table */
656  %mp_lockanytable(LOCK,
657  lib=&mpelib,ds=mpe_submit,ref=%str(&LOAD_REF Approval in auditors/postdata),
658  ctl_ds=&mpelib..mpe_lockanytable
659  )
660  proc sql;
661  update &mpelib..mpe_submit
662  set submit_status_cd='APPROVED',
663  num_of_approvals_remaining=&num_of_approvals_remaining-1,
664  reviewed_by_nm="&user",
665  reviewed_on_dttm=&sastime
666  where table_id="&LOAD_REF";
667  %mp_lockanytable(UNLOCK,
668  lib=&mpelib,ds=mpe_submit,
669  ctl_ds=&mpelib..mpe_lockanytable
670  )
671 
672  /* run post-approve hook */
673  %mpe_runhook(POST_APPROVE_HOOK)
674 
675  data apPARAMS;
676  AUTHORISED=1;
677  ALREADY_UPDATED=0;
678  ALREADY_UPDATED_DTTM=.;
679  DIFFTIME="&difftime";
680  if &syscc=0 then RESPONSE='SUCCESS!';
681  else response="SYSCC=&syscc.";
682  run;
683 
684  %mp_abort(iftrue= (&syscc ne 0)
685  ,mac=&_program 582
686  ,msg=%superq(msg)
687  )
688 
689  %mpe_alerts(alert_event=APPROVED
690  , alert_lib=&libref
691  , alert_ds=&ds
692  , dsid=&LOAD_REF
693  )
694 
695  %removecolsfromwork(___TMP___MD5)
696  %webout(OPEN)
697  %webout(OBJ,apPARAMS)
698  %webout(CLOSE)
699  %return;
700 %end;
701 %mend postdata;
702 
703 %postdata()
704 
705 %mp_abort(mode=INCLUDE)
706 
707 %mp_abort(iftrue= (&is_err=1)
708  ,mac=&_program
709  ,msg=%superq(err_msg)
710 )
711 
712 %mpeterm()