From 6a82a187735a7bf725a3b7666437e48b4da04343 Mon Sep 17 00:00:00 2001 From: bendodge-xwis <32881391+WyattBest@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:37:55 -0600 Subject: [PATCH 1/5] Delete orphaned actions First working version --- SQL/[custom].[PS_delAction].sql | 28 ++++++++++++ SQL/[custom].[PS_selActions].sql | 38 ++++++++++++++++ Tools/GRANT EXEC.sql | 2 + config_sample.json | 7 ++- ps_core.py | 55 ++++++++++++---------- ps_powercampus.py | 78 +++++++++++++++++++++++++++----- 6 files changed, 173 insertions(+), 35 deletions(-) create mode 100644 SQL/[custom].[PS_delAction].sql create mode 100644 SQL/[custom].[PS_selActions].sql diff --git a/SQL/[custom].[PS_delAction].sql b/SQL/[custom].[PS_delAction].sql new file mode 100644 index 0000000..883a99f --- /dev/null +++ b/SQL/[custom].[PS_delAction].sql @@ -0,0 +1,28 @@ +USE [Campus6] +GO + +/****** Object: StoredProcedure [custom].[PS_updSMSOptIn] Script Date: 2021-06-10 16:41:06 ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +-- ============================================= +-- Author: Wyatt Best +-- Create date: 2021-06-10 +-- Description: Deletes a Scheduled Action by ACTIONSCHEDULE_ID +-- +-- ============================================= +CREATE PROCEDURE [custom].[PS_delAction] @Actionschedule_Id INT +AS +BEGIN + SET NOCOUNT ON; + + DELETE + FROM ACTIONSCHEDULE + WHERE ACTIONSCHEDULE_ID = @Actionschedule_Id +END +GO + + diff --git a/SQL/[custom].[PS_selActions].sql b/SQL/[custom].[PS_selActions].sql new file mode 100644 index 0000000..206ae6d --- /dev/null +++ b/SQL/[custom].[PS_selActions].sql @@ -0,0 +1,38 @@ +USE [Campus6] +GO + +/****** Object: StoredProcedure [custom].[PS_updSMSOptIn] Script Date: 2021-06-10 16:41:06 ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +-- ============================================= +-- Author: Wyatt Best +-- Create date: 2021-06-10 +-- Description: Select scheduled actions by person, CREATE_OPID, and YTS. +-- +-- ============================================= +CREATE PROCEDURE [custom].[PS_selActions] @PCID NVARCHAR(10) + ,@Opid NVARCHAR(8) + ,@AcademicYear NVARCHAR(4) + ,@AcademicTerm NVARCHAR(10) + ,@AcademicSession NVARCHAR(10) +AS +BEGIN + SET NOCOUNT ON; + + SELECT ACTION_ID [action_id] + ,ACTION_NAME [item] + ,ACTIONSCHEDULE_ID + FROM ACTIONSCHEDULE + WHERE PEOPLE_ORG_CODE_ID = @PCID + AND CREATE_OPID = @Opid + AND ACADEMIC_YEAR = @AcademicYear + AND ACADEMIC_TERM = @AcademicTerm + AND ACADEMIC_SESSION = @AcademicSession +END +GO + + diff --git a/Tools/GRANT EXEC.sql b/Tools/GRANT EXEC.sql index d4d26e7..ff45b2e 100644 --- a/Tools/GRANT EXEC.sql +++ b/Tools/GRANT EXEC.sql @@ -13,6 +13,8 @@ GRANT EXEC ON [custom].[PS_updUserDefined] to PowerSlate GRANT EXEC ON [custom].[PS_updEducation] to PowerSlate GRANT EXEC ON [custom].[PS_updTestscore] to PowerSlate GRANT EXEC ON [custom].[PS_updProgramOfStudy] to PowerSlate +GRANT EXEC ON [custom].[PS_selActions] to PowerSlate +GRANT EXEC ON [custom].[PS_delAction] to PowerSlate GRANT SELECT, UPDATE, VIEW DEFINITION ON [USERDEFINEDIND] to PowerSlate USE [PowerCampusMapper] diff --git a/config_sample.json b/config_sample.json index e1ceaa6..f3c953a 100644 --- a/config_sample.json +++ b/config_sample.json @@ -65,7 +65,12 @@ "url": "https://apply.school.edu/manage/query/run?id=xxxx&h=xxxx&cmd=service&output=json", "username": "username", "password": "astrongpassword" - } + }, + "admissions_action_codes": [ + "ADIMMUN", + "ADTRANS", + "ADESSAY" + ] }, "pc_notes": [ { diff --git a/ps_core.py b/ps_core.py index c4b2a67..01b30e6 100644 --- a/ps_core.py +++ b/ps_core.py @@ -248,6 +248,13 @@ def main_sync(pid=None): 'status_calc': status_calc}) apps[k]['PEOPLE_CODE_ID'] = pcid + verbose_print('Get scheduled actions from Slate') + if CONFIG['scheduled_actions']['enabled'] == True: + CURRENT_RECORD = None + # Send list of app GUID's to Slate; get back checklist items + actions_list = slate_get_actions( + [k for (k, v) in apps.items() if v['status_calc'] == 'Active']) + verbose_print( 'Update existing applications in PowerCampus and extract information') unmatched_schools = [] @@ -256,26 +263,39 @@ def main_sync(pid=None): if v['status_calc'] == 'Active': # Transform to PowerCampus format app_pc = format_app_sql(v, RM_MAPPING, CONFIG) + pcid = app_pc['PEOPLE_CODE_ID'] + academic_year = app_pc['ACADEMIC_YEAR'] + academic_term = app_pc['ACADEMIC_TERM'] + academic_session = app_pc['ACADEMIC_SESSION'] - # Execute update sprocs + # Single-row updates ps_powercampus.update_demographics(app_pc) ps_powercampus.update_academic(app_pc) ps_powercampus.update_smsoptin(app_pc) if CONFIG['pc_update_custom_academickey'] == True: ps_powercampus.update_academic_key(app_pc) + # Update PowerCampus Scheduled Actions + if CONFIG['scheduled_actions']['enabled'] == True: + for action in actions_list: + ps_powercampus.update_action( + action, pcid, academic_year, academic_term, academic_session) + + app_actions = [k for k in actions_list if k['aid'] == v['aid']] + ps_powercampus.cleanup_actions( + CONFIG['scheduled_actions']['admissions_action_codes'], app_actions, pcid, academic_year, academic_term, academic_session) + # Update PowerCampus Education records if 'Education' in app_pc: apps[k]['schools_not_found'] = [] for edu in app_pc['Education']: - unmatched_schools.append(ps_powercampus.update_education( - app_pc['PEOPLE_CODE_ID'], app_pc['pid'], edu)) + unmatched_schools.append( + ps_powercampus.update_education(pcid, app_pc['pid'], edu)) # Update PowerCampus Test Score records if 'TestScoresNumeric' in app_pc: for test in app_pc['TestScoresNumeric']: - ps_powercampus.update_test_scores( - app_pc['PEOPLE_CODE_ID'], test) + ps_powercampus.update_test_scores(pcid, test) # Update any PowerCampus Notes defined in config for note in CONFIG['pc_notes']: @@ -292,24 +312,13 @@ def main_sync(pid=None): # Collect information found, registered, reg_date, readmit, withdrawn, credits, campus_email = ps_powercampus.get_profile( app_pc) - apps[k].update({'found': found, 'registered': registered, 'reg_date': reg_date, 'readmit': readmit, - 'withdrawn': withdrawn, 'credits': credits, 'campus_email': campus_email}) - - # Update PowerCampus Scheduled Actions - # Querying each app individually would introduce significant network overhead, so query Slate in bulk - if CONFIG['scheduled_actions']['enabled'] == True: - verbose_print('Update PowerCampus Scheduled Actions') - # Make a list of App GUID's - apps_for_sa = [k for (k, v) in apps.items() - if v['status_calc'] == 'Active'] - actions_list = slate_get_actions(apps_for_sa) - - for action in actions_list: - # Lookup the app each action is associated with; we need PCID and YTS - # Nest SQL version of app underneath action - action['app'] = format_app_sql( - apps[action['aid']], RM_MAPPING, CONFIG) - ps_powercampus.update_action(action) + apps[k].update({'found': found, + 'registered': registered, + 'reg_date': reg_date, + 'readmit': readmit, + 'withdrawn': withdrawn, + 'credits': credits, + 'campus_email': campus_email}) verbose_print('Upload passive fields back to Slate') slate_post_fields(apps, CONFIG['slate_upload_passive']) diff --git a/ps_powercampus.py b/ps_powercampus.py index 9548113..44c9091 100644 --- a/ps_powercampus.py +++ b/ps_powercampus.py @@ -1,3 +1,4 @@ +import imp import requests import json import pyodbc @@ -52,7 +53,7 @@ def verbose_print(x): def autoconfigure_mappings(dp_list, validate_degreq, minimum_degreq_year, mapping_file_location): - ''' + """ Automatically insert new Program/Degree/Curriculum combinations into ProgramOfStudy and recruiterMapping.xml Assumes Degree values from Slate are concatenated PowerCampus code values like DEGREE/CURRICULUM. @@ -62,7 +63,7 @@ def autoconfigure_mappings(dp_list, validate_degreq, minimum_degreq_year, mappin minimum_degreq_year -- str Returns True if XML mapping changed. - ''' + """ dp_set = set(dp_list) if validate_degreq == False: minimum_degreq_year = None @@ -121,11 +122,11 @@ def autoconfigure_mappings(dp_list, validate_degreq, minimum_degreq_year, mappin def get_recruiter_mapping(mapping_file_location): - ''' + """ Return a dict translating Recruiter values to PowerCampus values for direct SQL operations. mapping_file_location - Network path to recruiterMapping.xml - ''' + """ # PowerCampus Mapping Tool produces UTF-8 BOM encoded files. with open(mapping_file_location, encoding='utf-8-sig') as treeFile: tree = ET.parse(treeFile) @@ -371,24 +372,79 @@ def update_academic_key(app): CNXN.commit() -def update_action(action): - """Update a Scheduled Action in PowerCampus. Expects an action dict with 'app' key containing SQL formatted app - {'aid': GUID, 'item': 'Transcript', 'app': {'PEOPLE_CODE_ID':...}} +def update_action(action, pcid, academic_year, academic_term, academic_session): + """Update a Scheduled Action in PowerCampus. + + Keyword arguments: + action -- dict like {'aid': GUID, 'item': 'Transcript', 'action_id': 'ADTRAN', ...} + pcid -- string + academic_year -- string + academic_term -- string + academic_session -- string """ + CURSOR.execute('EXEC [custom].[PS_updAction] ?, ?, ?, ?, ?, ?, ?, ?, ?', - action['app']['PEOPLE_CODE_ID'], + pcid, 'SLATE', action['action_id'], action['item'], action['completed'], # Only the date portion is actually used. action['create_datetime'], - action['app']['ACADEMIC_YEAR'], - action['app']['ACADEMIC_TERM'], - action['app']['ACADEMIC_SESSION']) + academic_year, + academic_term, + academic_session) CNXN.commit() +def cleanup_actions(admissions_action_codes, app_actions, pcid, academic_year, academic_term, academic_session): + """ + Delete orphaned Scheduled Actions from PowerCampus. + + admissions_actions -- list of action_id's to consider + app_actions -- list of dicts from Slate, each representing a scheduled action + pcid -- string PEOPLE_CODE_ID + academic_year -- string + academic_term -- string + academic_session -- string + """ + + # Only keep keys we care about in app_actions + keys = ['action_id', 'item'] + app_actions2 = [] + for action in app_actions: + app_actions2.append({k: v for (k, v) in action.items() if k in keys}) + + # Get actions from PowerCampus + pc_actions = {} + CURSOR.execute('exec [custom].[PS_selActions] ?, ?, ?, ?, ?', + pcid, + 'SLATE', + academic_year, + academic_term, + academic_session + ) + for row in CURSOR.fetchall(): + pc_actions[row.ACTIONSCHEDULE_ID] = { + 'action_id': row.action_id, + 'item': row.item + } + + # Ignore actions types that are not part of admissions_action_codes + pc_actions = {k: v for (k, v) in pc_actions.items() + if v['action_id'] in admissions_action_codes} + + # Find actions in pc_actions but not in app_actions + # This depends on exact matching between the dicts + orphan_actions = [k for (k, v) in pc_actions.items() + if v not in app_actions2] + + # Delete each orphaned action + for actionschedule_id in orphan_actions: + CURSOR.execute('exec [custom].[PS_delAction] ?', actionschedule_id) + CNXN.commit() + + def update_smsoptin(app): if 'SMSOptIn' in app: CURSOR.execute('exec [custom].[PS_updSMSOptIn] ?, ?, ?', From 1b1afda9e79cf7979f9f4fd4081082dbf3621981 Mon Sep 17 00:00:00 2001 From: bendodge-xwis <32881391+WyattBest@users.noreply.github.com> Date: Fri, 11 Jun 2021 16:44:38 -0600 Subject: [PATCH 2/5] Autolearn scheduled actions --- SQL/[custom].[PS_selActionDefinition].sql | 32 +++++++++++++++++ Tools/GRANT EXEC.sql | 1 + config_sample.json | 1 + ps_core.py | 43 ++++++++++++++++++++--- ps_powercampus.py | 7 ++++ 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 SQL/[custom].[PS_selActionDefinition].sql diff --git a/SQL/[custom].[PS_selActionDefinition].sql b/SQL/[custom].[PS_selActionDefinition].sql new file mode 100644 index 0000000..ca1cf56 --- /dev/null +++ b/SQL/[custom].[PS_selActionDefinition].sql @@ -0,0 +1,32 @@ +USE [Campus6] +GO + +/****** Object: StoredProcedure [custom].[PS_updSMSOptIn] Script Date: 2021-06-10 16:41:06 ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +-- ============================================= +-- Author: Wyatt Best +-- Create date: 2021-06-11 +-- Description: Select action definition by ACTION_ID. Limited to only active actions. +-- +-- ============================================= +CREATE PROCEDURE [custom].[PS_selActionDefinition] @ActionId NVARCHAR(8) +AS +BEGIN + SET NOCOUNT ON; + + SELECT ACTION_ID + ,ACTION_NAME + ,OFFICE + ,[TYPE] + FROM [ACTION] + WHERE ACTION_ID = @ActionId + AND [STATUS] = 'A' +END +GO + + diff --git a/Tools/GRANT EXEC.sql b/Tools/GRANT EXEC.sql index ff45b2e..81d341b 100644 --- a/Tools/GRANT EXEC.sql +++ b/Tools/GRANT EXEC.sql @@ -15,6 +15,7 @@ GRANT EXEC ON [custom].[PS_updTestscore] to PowerSlate GRANT EXEC ON [custom].[PS_updProgramOfStudy] to PowerSlate GRANT EXEC ON [custom].[PS_selActions] to PowerSlate GRANT EXEC ON [custom].[PS_delAction] to PowerSlate +GRANT EXEC ON [custom].[PS_selActionDefinition] to PowerSlate GRANT SELECT, UPDATE, VIEW DEFINITION ON [USERDEFINEDIND] to PowerSlate USE [PowerCampusMapper] diff --git a/config_sample.json b/config_sample.json index f3c953a..38f0cd1 100644 --- a/config_sample.json +++ b/config_sample.json @@ -66,6 +66,7 @@ "username": "username", "password": "astrongpassword" }, + "autolearn_action_codes": true, "admissions_action_codes": [ "ADIMMUN", "ADTRANS", diff --git a/ps_core.py b/ps_core.py index 01b30e6..126ac2d 100644 --- a/ps_core.py +++ b/ps_core.py @@ -9,13 +9,15 @@ def init(config_path): """Reads config file to global CONFIG dict. Many frequently-used variables are copied to their own globals for convenince.""" global CONFIG + global CONFIG_PATH global FIELDS global RM_MAPPING global MSG_STRINGS + CONFIG_PATH = config_path # Read config file and convert to dict - with open(config_path) as config_path: - CONFIG = json.loads(config_path.read()) + with open(CONFIG_PATH) as file: + CONFIG = json.loads(file.read()) RM_MAPPING = ps_powercampus.get_recruiter_mapping( CONFIG['mapping_file_location']) @@ -30,7 +32,7 @@ def init(config_path): def de_init(): - '''Release resources like open SQL connections.''' + """Release resources like open SQL connections.""" ps_powercampus.de_init() @@ -93,7 +95,7 @@ def slate_get_actions(apps_list): def slate_post_generic(upload_list, config_dict): - '''Upload a simple list of dicts to Slate with no transformations.''' + """Upload a simple list of dicts to Slate with no transformations.""" # Slate requires JSON to be convertable to XML upload_dict = {'row': upload_list} @@ -158,7 +160,7 @@ def slate_post_fields(apps, config_dict): def slate_post_fa_checklist(upload_list): - '''Upload Financial Aid Checklist to Slate.''' + """Upload Financial Aid Checklist to Slate.""" if len(upload_list) > 0: # Slate's Checklist Import (Financial Aid) requires tab-separated files because it's old and crusty, apparently. @@ -176,6 +178,34 @@ def slate_post_fa_checklist(upload_list): r.raise_for_status() +def learn_actions(actions_list): + global CONFIG + action_ids = [] + admissions_action_codes = CONFIG['scheduled_actions']['admissions_action_codes'] + + for action_id in actions_list: + for k, v in action_id.items(): + if k == "action_id": + action_ids.append(v) + learned_actions = [ + k for k in action_ids if k not in admissions_action_codes] + + # Dedupe + learned_actions = list(set(learned_actions)) + + # Sanity check against PowerCampus + for action_id in learned_actions: + action_def = ps_powercampus.get_action_definition(action_id) + if len(action_def) < 1: + learned_actions.remove(action_id) + + admissions_action_codes += learned_actions + + # Write new config + with open(CONFIG_PATH, mode='w') as file: + json.dump(CONFIG, file, indent='\t') + + def main_sync(pid=None): """Main body of the program. @@ -255,6 +285,9 @@ def main_sync(pid=None): actions_list = slate_get_actions( [k for (k, v) in apps.items() if v['status_calc'] == 'Active']) + if CONFIG['scheduled_actions']['autolearn_action_codes'] == True: + learn_actions(actions_list) + verbose_print( 'Update existing applications in PowerCampus and extract information') unmatched_schools = [] diff --git a/ps_powercampus.py b/ps_powercampus.py index 44c9091..4a37268 100644 --- a/ps_powercampus.py +++ b/ps_powercampus.py @@ -372,6 +372,13 @@ def update_academic_key(app): CNXN.commit() +def get_action_definition(action_id): + CURSOR.execute('exec [custom].[PS_selActionDefinition] ?', action_id) + row = CURSOR.fetchone() + + return row + + def update_action(action, pcid, academic_year, academic_term, academic_session): """Update a Scheduled Action in PowerCampus. From f9cd58ca54de2b0ea66c47fad2f172d68c2f26a6 Mon Sep 17 00:00:00 2001 From: bendodge-xwis <32881391+WyattBest@users.noreply.github.com> Date: Fri, 11 Jun 2021 20:23:22 -0600 Subject: [PATCH 3/5] Update PowerSlate Integration Fields.docx --- PowerSlate Integration Fields.docx | Bin 32857 -> 32640 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PowerSlate Integration Fields.docx b/PowerSlate Integration Fields.docx index f733b563f4b3d7cb29fe1c7de5c8c8a9ccd51a5e..559ca0f833b13d3d17c9ad1b19dde159c8112786 100644 GIT binary patch delta 18487 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%0D&Ml^q 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 KAW&bz&)z=}&4x7q delta 18748 zcmV)oK%BpT{{h*606Eya#(BQGI^8_s|Lx{ z=piZV^vjbB2vC2g`Rsey@GPmb+ia56PqIzG7R@AC{^^7s$)}brO!4^W7Wmb)7^f3F zUbIsD@Md1-@7m|TUp}w8{;qkx{a8Fv9y0#sk*HM;dgNDSI+1fxo)J)~7!9%*=&i{1M^n8DJix3<@5!J-3f9Ikn zQ^|#^7oRjWoymmz`}r_O6;G zBRR}jnO14}IeqiqdC2Y`zMrI@(}~&8uBQqKBR^;9qf^{DcUd|aS5EbmkJ7Poo7RtM znmgCUV_JsEbT+RX2`QZ$X%$lE_Q^3`BMDO6iBfgDewxX*RI_w4xv8Z!esk3sr*2O- za!`K*A|H1j-!hCWuhaWdo2$3aoX5B*o%BmGs+;+6@?@G;-#Jy9nj_xS^5W8UX}qqy z*3V>mELm7xHSIK4>-V$jcWvpbx3i~12k^c>RQd00oq<6VaLk~=z%&xf_oAT3z(8>1 ztuD1g=wIw`b^ngZ)w5<~?Er7}uy1?AR{ei%f3)Y9TZt7Glle5?N|)XU_P^u(|4sXI zzj;QP&qD#I8hKwO_@ueonNbRaSTRL+@_KjlK<-HH%#Wkp|T`eClroMh3YgYeNviiH7+_##_I#H8X z3n?sBP5Lj%Y$olOb5qRA(e8=wbkmsl9n$gP&Z)~JuUZ)IB&C}B&&A|(I{wa+tP2<# zUk}kzQ1F;gLrXCMIEvk`RHppv4_7z;_Vbh83n~GplL-tfH0*pnCfM_v=if^}UuXH@ zFMR*U%|g0aJS8}8KYDh&jElUkbz8gY%%1#1;@GW- zNa~Q{TA|-QI_f?p|8C!|zV($S34E7vIijBO#BLBu7sc%<&sb0vuiTc7@@((u);DR& zkH3XKrHKkLw^Q3!c7Kbh*~m;rcTvm<4Gmq-851}NM6bIdV1lc0ozh*cpS9Rl?dRfV zDP5&nwK|a_N+n1f7y9e0kgg~M+|%k_jii0n$hlw1yn5^S8oz1HZ>_oWDIde}DVC^S8Oyq{}Cb7Pq{< zN)?srwA+IRd8eFY`6ngz`RJjL6ZZSZVnjJa7AwC`&-6$u_94&eESWer z(#hSWIonQm`hU0T!sENg^!BH0JWlg|hqjxMQ<^)NL!SGhecW~J|0w<}Vy8=V#bw+0 z>v{bddj2-C`148s9-f<K0)fc_1*|)!6f-tBwy;x739sZo&>< zox0tlzqEmTQT_Fo*;w-9c(?AllPE|eqX(FCo$cBG@UV*f*QV|CUUbe1Hv)mdMS&SI_?FjoPo?#eKW zee4M>uz$;L9uzigM%VOF@%ygm=E0+w>3_S?&*tm;a4?S{@}N?-Jrx}DAi{BPIIfR{ zSZU=(*&LO=u`m4ChDsMpMD1^Gi{g{YG}K8EjtWc5wp6=nS=0!s>4{tNO0SEWuQrQh zi_3Pg9C48v{EkLiRJW@UTdGk@3cDJ#vP0q~tOKYsSmKu(Ye34&~R$;Q;_6KzAgURd|NPjcLYC{*;xH~ zFMmH}8%=jHfwbvh?=pqBU1IHBhKOyt?0LG4=6#FT)IEwZk28NI^^N>2FLt#cj+n=o zv3SA>6qMqnllf6Uk6g|uYS^GvW25}8M=5Cmm;XC%zV(&0`7Xxec8c}a^mCebzsuWQ zdZ^po?~Av`YcE>d`PqD7+r#xI+8g}`T7N1mjNPdDck|Q$u$r9y)O6cFoE2aH)Tp^= z#Z>dXV@Ro*x^}Yqwu}cc3{Y&k6ESB|i1ln7$mnafti7XO*+S|T>*@aX6Vq0ftK8WB zp2w92TqPFuG~&{-FIc9_ILk@XTH)YH1BitI1qPlW5vPQcKAw5@q*<{ansI(D41YiD zFqY94<6xG7F>@p8DP-Ga8SX`)i2B*RmStW&%e47)yMSDqBDTM=Ehw)^)jNSN6=XS> zEr}m+?s}&BCm_a@^phQrWlN+3>Xt1}V9NtIuub{f&L8Td`E8FaHvv-;dOUU`HS1kq z8h8PW@sbG+2Br-~8|wDy+MS#M=zpr&#sbrS3}DJ4$!IPzk_os=aL8Si(X~}Gb(9bU zoPj2twUwLeGQG>boS;^ZLx>z8$arWZkpn`6&j>oc_ghrxHz{hpbimElGb+;Ep{b6k zuixiIt#>CqmRX%T>6fgkRaSA9JJ;dc6IkUqWXRl@3gopqdCBNujAGvfy$BTi~zz2`LQ@* z1UqH^6rOsf%)b~whn6trf4vTtBfxU07nDOzZ)SG8fDPT!%9{G(%w^XdLO( zHcPYf23jYymssUS`x4ouY#LkNh_sl%rsm0z;)SkmJn zP=zo*6nr6V%dmtSs@Z$!ulCF);syyZ8D2^9C-Vpjh5K2$tF`C(*Ue3j{^`0Y^2Cpn!O5}j3D8-spe$E z2Paa-h7ZmRR8WXnOnBP9WWhy?M$8Gl*bWb%eR#5BqJ6|;vh z)PRCGB)Vcpr}J2X43ELpw)cZOZ@rb4olJ4xoO$Sv$I^PIkIt;jrb)S8ueM8oj#jPT zUz@+&@s_l#R5?>8RV>w@0%x92(rS0sWA}ZV8&1aKa#Jf!7n4buCgZ0Dk5t3s5FTRg z1K$9hgiA&SI)7o(^78X|c?h@>2v7RnhW$Z zFh_zc(_?J3WEW*@wB+0vETkSIdaFbi#&{G%;^|kOgu$*Kiad4xFrPVf;b149Pj96y z?boXw0+o@F@i_7gt;kU1@__aeFP?A7zHKMW1Yk2o@_(ugojAXw<^9g8OF_tCYkKPU zmGhX@53Be~%P*=zccspkP{h9g4Dtfzhmn!B!`;{wp>Z;hWu8|8gE);j35Bs}1U;Wa zfL9@wJv6f$K`sY_pzFqxU`Esh1tg+4UM}<5sLLMubOh$e^${jGMV=us6hayj?*tRM zvIoke9e>=yLrghkNIlU7EYSC*H`G5G!TPG_weUX`(iATu7nfQhAr$#xXzbbqz{NhK z=eEh;BDn{!$rN}#WNZ6C;($g0I+$iQG;ukbOr=0^kDCENL>YncQmNX|#4gI%(8QU6 z3iAZ>ePS-C`XCnFvYtJjJ^-lvsMh;)b&R$1A%D91_~F;9o10$;hbe^Yzycpex|+KU2t1Ew=La8= z&oN(H$?}j3BYy|@uy*!Lc9ywujP=RUU5`2lxEs+EOjFAqf6c07aI=a7*N=U0$lg2a z9)G?TvkECEv1guNM&kg+-oXslsrzV&W)N*DgO^Bq#DH*HtEuTsQpxM2@{Is$X#Nhk;q80tfsyYn9sE`jNx4nx<{H|%kfj*5J&mZ>jkfPWDR zy_o3Yb~gU-wHRO|aKu7Qp03WOoI#3oJ2w9CETUb~088UZV;UG)^^gMTO#~~-?U6tm zf4H0hW;Dhg!e-3YBeCm8OY+$G!!F9$_`{h|iL~KB0P`S;(+qigM;|NNIpDLErVS@O zPtv>2$<7p^MOXDt*90*v=6NlFPJeOd1X)=>432V$55hojmAYla3||Y%k=BpnkQjN^ z07Dvwsd;?jyoY z%!54`GI&CQrFETJrd=cO$A_$PB#@!5K7Z>KVW*q)0;h~d5stXt z2xa3RL#Wjuuni6(1YKchR)PW_gM%Fa8~=DU*amum=L2eB8yrW}rJZ7$dxX%2KrV-E zxb!0<2F$~mA#nlHfe{-58Ptr+Ne+PP8X6GBh_k>yI5+7sX8f0=%ztX>4&T&CJs-Y` z1oB;D;fm3vFGU>nO$N+5`}nW z9BioK^_X*ry)cfjv4tJ_AjF|IGB#APmohd~ab}D|JODWdYfR}gNWCN1FxoqVVVu9F zby_BQPZ%HjRS|tmJ%6^da`aW(%9hg67s5}}#W8dB1b3z#`LX8*=+HoOCyCcOV=Eo8 zV4B5@t?x0~QWE|8gX6w+-{Q@zvH77YO{d$7nQz$MbXyjm(ls95@rFMg)A}r_s`daD z=saeViPN0Sg5;IDaQ@Su%_i9>sWT(qAZM*CiaSxN#v2yCFMpG%l!mfy^vkZd{@2_8 z+}Bz)ODB^~a~oeL^6~zcweq_`P`ADIpl1JTvp#tHJZ1U~4zzaU}=H^C7LcFzQMA*ICinUMv!F=}Pdv?E6S zI~weEXtmeywS3m@q5e6^@8`){rSF|~>@fXuizNJSa#Tvak&BkA@&rQh%Zw=_>q|zL%!2bOMeB6&rJ+ zMT|Y+nS1qsPbiHQX*>AAMi6ZT@gkrK2!Nn4D>!gW{DA9oaSrlLE)zkFehW@ev`1!z zOE1e8X7blYO>2{#Udz%bA->1CE}qvV8l^4|u&q>kDWV;Df}e{xiq=X(lTf;O%0}9t z$n$8nRDYXi+tU?rj5sjjABge53-s>TE%?Qn>hqXt3O#|CZ+Q8{V}w!v$TN4_JHu_# z*8cC{)*1&`phy^ydLV0k3X?uliM$w-4r#3+mk=uyhPB4r<2-;gj?7(h(0}z1kr;YG9vg<(MHw50I5V~Z#7CYh z3~vBHbui*;BjD@6HrHjL(jBg%^uA2ft8iYH*=R0jVQ{#^!Y~e_&`4>w!HHK5cc_bl z2*&2=VzCIR(2JRe5Y47?ypn|&5r&X2G6R-Aa6nw{Q5&^*g>Z*4=~#Hs^fp)^aIC}L zHh*d{sAcEWcOf!2qH;XS0*`knHN(KRdEjXEEkK4vzf*i%^8j+#7&7 z;tjYsh-Ct#3V`gjJm0c2xtZR7PDjObn&#tlymgaSM<{o+B-yx1YbSBW4PBf+d=JQF z+p|epr&rhV4U*UYsFMvIA_%$2#+B1hixJawv0T1JZpqLwghjP#Jaoo>Tw5&Q@Q2RJs z6GagdYcXwvVZi);7}U~ia9pE4ZYS$hLL`8$=N@w6;&HZgjd1@xT99m%=5<=OimJJJ ztH0caRWm+)wa=StDnTuDA!b-F0ucc~SKxnNbsV{vMXR;+RwXvZe#2<`Ki8vaFLg#U z>oMU^(k+x8AncRpwkSSLlk$_+_wuLNxCLu@GL-`K`^RELIYbzE?q;pM!XsO>cHEqH zuu*q!e&R2)u{0=S=dLWK?db^S)7_~P0TWW~HhlK|W*hNO_w}E8o0RX0(hbaftCi;4kWckPBc9QDH^bZym1-Oj{=&2|Di=X97 zm->SEhMrjB=cDj-UUcO?FIpwB7)^HsKbF~8{dq4xWgAU*HRRPD>|NFnZvC=0m+P06 zLSDXa@tQ3TOqa7ZE`lKGhr-wh9`1klYPKwbth^ahFYlZ0uUIeJMUc%?)4>o&*4&Ll zggm!p)nm+)!RUwBESIq`>iK^yIWs+kY@>Br)7qWfi5-tCrKq*H^J`*&`@^ut25twrdm#J^3a8VnKwp{eGT%>=>%Sar% z3Fqy8a$C8(VqT6;;mhNWY4WH?AvHWrtK424TOPoH(wDtDr9Mhqys=N%jlp{i{CF?3 z*jyhAb1w*UNl2oY<3o~m4My)MtNtCWtU4u47xL9vbx@(@6Lj;DTZ|Zlgoo;hp@wEx zJXfN5L^Suvig0o4yEs0?$Xb6oUX+fD*Zwaj*cs2&#t;V~2-GL8^oAqu2XIFI_V6yASI;UNt>7G4~q zeMK5<#9|k@Y{cTsXh&k5AdJl%OpLtm^@^2Zq2z zkt-lJE9GzuW8%}*Dv*CA<5!1?Pz;3U?mM5t20Qi;?PT*$d=`?xHzg}%+zX9_>@%4E zjobT%u>F{Of-+r2#)dmymEJ$HL>i!B80d}fU1!*%A!bG`a7)LtEB;G_J0Oa2Bwdg` zg{gw+!n*)U|F$VS=sE`Q-3aa5kzm6eyU1n39p}b7fy)>U%?f`(5QQG_^eczJJC_M~ z@W_w-I5q^1#+)%C&J)V9UeZc7UuD39@>oPrQ=uF50>BkvV1r(R@W245NS)geh0Lf- zz&yk;3i^94Eg4?}Dk3hy`cUqDsED~fj0m&=j~4;}h%y8OH2{DrMJocG9#;K zoh<+$EarKYPKtkWaKn$IFp4Qw>b+<911=eGwKeuRW3oO}%Xn+r;#&N6{j>Nz{4El{ ze~1P*@Cd3i?c6=tVV2gf-M}N{NkwGDU8Tsfh(~747E8vLH}FTSXwW!z!?oiJZP?>* za@nxQnNbhqICOzoXpnOlhJn{d2+s_AM5%mdJ(ZsC;IMxOaNrZ7&l2nE&?tl;4DI6n zSvl`GQ+On#iG>fDE|J(}PU^)J26lCS5IU}G;UyM%2x0*z+$ez!P#_>PD%4pDe^qFQ z1_CoTI(m`E8ul|8b`QHQ*Ve9U19^;s_)IbXmW_Tb^14jMnc8uhOa?dZ$PFSl&}O7N zFzNn+uql81G_1-vh5MeVGG2`PhBWTL_uK%O@e1q#5k%nyBH7;}9WQI#p$I$ym?BSy z!jRz@cw!Z|uoV94j5`TX6cF>|5*x}mhFmt3ac;!JrDj=Mz}Xl8e9IqK-&@0^=dpYbs`EC3#jfNv@r0z-;jy=Uk7Q(|4KmTygEv$+!HZ~5fO znKggFFs+>IZfDi?9t-sng0sp=awi#$(pjD4qtubJK2P5|E7o)>l|8yD@Ba1#ZaXq} z6msHYv+xb0K?JFh8fCfd>jW>z7aoeu@`Nys8usR2_GZ&mvA9=#n$-%du!IGxJJM#&`2 z$4NOn!h%8M#xXN@Xd&)$qB909lYHd}i-|Bm#I(c6=ZM65A;nVobs?;S7sHqr2NZu( zW23qa4_yjOP!9^9>OfG}J0${3Z$t%+w$k+2bsyQ^NqV0;eqBquFgS7mJiutJ1P)`E z65rOiI4}1tRK^g-UDxf5so`8>AK(yNA|3f#q~m3cJC3o4gifpLQaBP62DVno&=el@ zFc~+b9tn;<3EqZJR^+nblQSb8iFbdP$AoIU!<3M-0J-8ksgtd2i7$1TjOL|uqUV!j z_zlaP$1H@vNOwV$i6{UUfIK$cUq5OM3AXxZ}ChiehtIB|bEPRnHC zgwjR2FUqIE%{vSzU|?-S=isBZ&Qr^;Yf9$hLsqFZkZCebo%zfu?wm??OmW4XI=oum z%$?h!erOg=9NAsxI(&NqryN_K-2*s^Pso?GoH7ij{JEIdSw4Iy#q$}Az@BKjCHz-{ zL%QoE%p)Fl4_YVj0_MA*fgFD}l(M3X4W*nJhw(5(2<W_oNHZ1%AdeM-F7h5-QCFFxe zWe5en%lV6)^=Pw921ieE9L6l%vs_^*{PhKF7y=AO#BM@*A)vqnr`UhfHHn6jtR-VK zT+q2wzrQZCX<|fZ7L2!fL(10uCmUK1vpV~GnpY3mY;Z7v5EO)x7gJHEaQib=)QfTZ zkYIwV_$Br&9bdxK{KqdlOwCpTA6dPH`#y1pRCt!Y+vq#&`8%=e#;&Il1&)FeHW0HS zmkq?68DAm7ycqI53zC1&!&j|rrK3Xa>UeT4341^+5j>?YrcP}PZg5brq`TB)jrnQga?1zuoEQq0v`sJj)Tzg zk~jcJZU}wP@JAquLmKFlG%bB!KMnve@I;J_{N?>Pz(zV&l(CVHGlL@*atg^Qsg1ie zONW8a299phFM}f@h603KvseKh+N>6*D>t6d@wb z>E0vkEQtppv44O4;XI&-xBw#UhTGuC3)p`se@!4UBLJ=o{NTLDVm#me|23cPMo@&lkr0HS9|EKgBeV2< zU8o3g!ot<572ES%T0D zJ5ehBX!?It&CxpgAISVFm6o$-l=el@I%P^VF=9TkHGMl9rs25ND2;z_%F+^P$7?OSr z(K5hS2r7^Zd=f%Kz$pP#P+?SyxAgs*Km{f^2=ITtZB=&j%`S4;_{*7*7K$lwg+5uL z>!Zek$Ab1F_d_GCpNeVvT3{BD1E4TaPjs!U08z+w>BNDx@Hg$+y#|=YgV^<9Xygw9 zmtaDS7@TE*uMlQ|kRj^p15>*K&XG@iy?epZ_jSQ69s@sI1x>fZEW%tA(0$2hHg>az zXdQpSIucQM0t!ij{`{j@oo5F6u^WS?9m(q7F(s>IqaTaUX_-&e;cg$I6BNdgkr&@1 z9thMEU5ydK(35<0mT_6ftYyA6jq&f3BX#$BsX$vmzgU>obKQ#*y9}JJcwxx3!YezEb^j`akomOe<$JuWEG$@-BZR zyOrR_o&praoGvu5CHQ5n=q2jS(hvp=!$aV%rSNOONk$6Nj)Mh2!xX{r) zj8lw#e)f(2mc|2t1>keisb#dIqAYcN4|01-@-xU~gDPi6P!Q6P_<`Y@&=8==!>g6t z4UFDVH~l+X5R@3t8@uVq0SpeJVCeg9gpFVki9_KszrUg6)Nxs#YbBQ(ylQ_Z{W2@c z+NmCj`DE_5+>9QIV&aq% za+PC=v>xQm$F!7<6t(SXdV^%*n@`<9nH`lNny$qJ+RVuqslr2X4)AG^VC-u~K z&N#g<)AT!iWaFqk!h1e_E5U!Cx^7&Szt+6r)P-|9N%Bv_Lq1Alm&K+tfVmLR(?^6_ zKK|+i=K24>`82-#b2xH_@-#n`h$B1M1JmY_}&SJ}iC!w7E zD5RD@|4#qwD4I7&M1Orslk#d>yM7oNyF?LrRZ%h=$R|m4^Nu%MW|_ zVKXJ5K!62iu?8Yddca6uGA$*}Etww+U8`JaW91jLj>bwtlc|(FeH*CzjR+qiUyKP9 z@ZgZpyrt@(@`XYyA`*WamYqe?vLo2{@7)jx4FT3D(Z6FUaj@k}r8T>A@&@KORRepJ z?=Gb)8I6kREXlLRzExR%KT#Q{vxlVlrS$z*U!)H&Eu?&`s^z!VrGI?Lqa z4@uRSPTA(lWNs4NeK6}dCmcW?h|oNiNP6Z(VA?;a!NRO_GuwZKoN(YHhPj^QE{!3G zgd1NV(DH37ltI^BBn2Re?Yhe|$Ys}EHcw4Q+Z-2}AlOr-Da5)7d0dyUx<_u2+O1T$a$>a!$8e-qF?Dzmrf>e$MY7w!Bl? z5|xyrH#i;o()Yt-NJ<5RQD9o35P67WWw9*dfAtuWyFmH`rc(+efW%1j!vmJaFM%P| z$L(aDa4ZUZ>CPE(9S}#DdqQ_(Nw_7#Tq9injwW1tsY8FbeWg_i6ybmd2Wm1lUmZ( zRZQehGu3U?t_s8b@aFfA#fWl4{UYIhDda^)$R8hhV-KT~5bZ(BF=)l7|=ixU^PDZ>~W)ju~bNbF|xL-hYT3~9-k z=^U?U=g}&Mp?OJqxu#Yl?P$D=!LjQl~j`mcN2Kr{p6OY#*I#XJ%KNe z+fpLDFm8%LTjlne+42Am^shf$omTo|llQk_wS;;B^syPBwW}o-=DsA%4HyjxI~nF4 z6|`AT?z|SiUH>e84}Xiq?-wYahI|z9C@_DrhJi~3^R!@|JX#PF>>aDGzey49#}Kskr2s{dMSyHtWV72x!?5MpfMaE4d)@BWFG(woMBn5KofP3ygPMPk zL>_W@IGi~%ndsb;;HfuV=)XJ8X(<#ai;=W*p@^;w1RuiAHlAESIe%1g~6bUJMBH7J&GmWe-Zc$GLCEEP$a6#eVSlOPHS zh-n*vzEc;6pGDRzazH5{F*}D9se+M!2*74&y@&BVu!km7+ru6(0yv>uFGYXv+c^;~ z@7uXFrAm21xYQ4?Ps&iH%HUW^9nFc}`mnO5RFC7?Tt~t>CsZVoQW;D;r5}cz^GM4o zx9QXwgz7_|z*-QhYS2yl?RPVs{XwyHLUxzu109W|=-7XGS$yuR88$ZgzFhnj^ovC~ z%derTpD3$M@B!<7)X58n}K`X_=qPG@eL`E6hOX`7HZQue`-6;Y)Bo5 zTo#kb;<@s?xZmy(5>yhqMk)wd6h^jkpNoCsBLePVyN@u3YLQx(y^w!0hy>FY-X6yH zwcWSAt3fgm2qNpQzIC@984r~K4+4T!kNtYV&LUtp~{PrT zrzARKvU?{%3)XO@Y+{Uf7ISVHG|XkHSjA;bwa0iD#%`l{Af`!bBkLVO*CIhEg`5y- z-Og#TuD$T7^?-r4&Ao|A0FcJhBvT|#w&hjNkY*x_AN zqK3(8&hT%yx8kelvWEw8*Nisb|H?{Mb|VdbBe8t;oKGg3+3L^zu2xM^^R?SA26o>1 z_NP?&=?6Y+kPp>{InXLD{bD*CKX#lmBtSALS0S+V6(O-!eD1?6!2yrMY(Ywd?!R6g!7iNYqL75sY>lQ79sUX1AWV;wM{4t)724!zK zb&hFX6&9^)EG>Alq#lmEH z)hBda@!*Zjo*=Ug@xg)m7{ zTc|9=5hH(UBEE|ce*tK_NBL$L{3;f~vNzq39&hXIHoCFf1X|B=bkB=mmg}0aqu%sU z9s5*=7x_&8w|Ud2An)}@!O#dB)|}$=Q68}VT%8oHdbra(*VS(eT_LAxx2k4UQzDrK z|19$I*Q#`mZejIDccjKME7;60tAW;9Q@U}5*cX3Ozo)c^rPX7$Nmh032kjd`_2+Q> zvlGao4Dz2ntsnpCG=vX)Rhd6_buqpZz~BGm^W#pGMLfRCr=nxP{vQAU|Nrb+Yj4}Q z68$TLKU~}e+E~=vxmyFW9=ieBt#1+evK9a(W4rwy>1qF-!@ z)MJ0<%$YMoO46I($M@%Is(}3(d^@V9lCEwtqUhk(QVOw*r9?2g zDd~q%RZ`C5A{#Z&X>Uef*ZjOHNec6&q!pESbTmCf^Nq?yfl`rCbk8`4qjgCnMJlK0 zj_{gRh!hzT1>c~G!cm%&Vnws&s$1@|xa^um~f9-#L zJac2u-VH@`NouYh&&+#6%?n#l$}6?u5VdoN#D~$Jlw_<}VI6dBS$oYwh}$)hw^xct z1(!0g#nR6$$Qr=9xfZG8Vbj8f(PzsvVFf8tMmP#shQgetx13d~#d0q0sZ4^4=nB36 z*Gm1Tg=r9(?VEvzyZ3gcJqFEnW|n^u+2KBDrsFwg9O4r}3oK{iI!}Rij48m}B_+UD zidcncK^7e1&vaT8OSWnffRrU?DNzivB`4LB#2&m>O&63FCtSRr82u+a!EQ+Vlvb4{ zYA!{+%F&--ClDfduoQB)R$>juDNucbfbwfn)cW{T`wzbzG6^+=5X(Lt)~tW3fP|m{ zX}zX}sx+Qu%{82NjG(LkengcL5>6ZQGY$EWQ%b`>#P_hsHMH)C`yx!Im8vr^@J7=A z)eP*p*_r%8b$uCb4V4}#%o)0B&4G9UAQj~_RWLt9X0bsFp>i~jKhHnLH{s_fzPX5+ zXa4c|OMLm)r}(dH7&%3!tSEo9Jj*rrQ_l5fHug;8RdniD%3bGMrG3y@;0gg=(DxZ# zvVvysG!@{4vo&kr5E7_-KBPttVUe5_w@5ZRBQG*vo0Kz6wateSZmrp8L+E2C^82Mn z?(6CHshw)=Dp9UfS)Gr^il%uXc-`dL**7SSw7NZmIyKJIF)iNL)fj)s$x=?Mv58&m zIR^HJf!W#K)7~@mSr^-$;8a?RNUGQ!?Vy5$FSa!+CwEP`W)dX?0&<(w?XmvPTlSPF-0Sv7cH+c|}by;_#@)XAm z!*NVkyU=&cnFSbo$@hOd_K4-vYXK3;8Es>M$U?wT(`tb6imqfE$#z}@SDL5DRS~`{XTC2c%=Q(sJvW5(h+HJVz3^EG+TkEO5^)W7xQG)+=k_g0BjB)G^ z;c;xw^B=JIad-}kK<5uMkvUP%w2D0{J<}?#*(z7VhaJ;oX!?IdsiOn!jz*p-0-1LT zevX-Vli1e^*l+d{(+)j**NVJ;-h71oo`Vg*sxog2QBkiuSoj^^Ue`5sFn6K5ux8Bc zlC8VpQQCzdElTe92Zy~kjU<|Qu}-6Y*A^J2fg|s2dJi4Npp|{X_rQo8!ydNz7LLI6 z{Wkxf@B8!3RSSPkkV4zCX6ZGR>p_AYZIcc|h!X=lelnnsZ3Z|t-DZ*U6GQkB3I+dh z3?0vNXR}BXt}o!8YsNTvYXbCA4Chk7`(J~JhU12Qq#@{`h=XJny7oik`=*3!E}@D3 zdV-SKUV`Ij5)4$_HD-}#{6G)zx2kx(Jk5DG4ieXy==Fa>e-(uB3>)^*#&@qP`Ntu0 zf2>J*NyZ&?_nGHw)ISla+bCU5>lZDlpD4G9?bwm8t3qGrV8jX?y=-|VPWyX9Z!S)Q zFm&FW5QZlmmz1~bzMPfU@aP}U1kVp5-)lBS zd!YiQPQrf}&wlz)eRm|<74iwWU_~=Cb}cOy{mwoJoG=XZcG=I@QgKqLOTB?5X*MS- zI+K*#Hq+2-Wf`oLtJfi~=>)zLc!TX2*9g48@%38jmy91I?-#15B*h8r#ewe`UO4FH z+{AODiE*@0zTpMIoXScR^IR14b$E(BZ2Rs+?_q!RHNY_g+nxNXhu^QlNzFOn zbZ~50raRcUb{#7U;@Qu-4HGiOz8(Ll*pn7D9pc!Rzc(UJ=j(>Rr9@Yo&eev+|*n;@!TM3(8(!*l72~-P}hr8 zGi%`;J$x^lv_6!pt8Z}CJp|M=+vRT#$Bu7zSd}YS*Xieo5_-<=GojvY-<>Ul zQewS3<#hQl*V?l?T?(Okr>j~ur`@Yk!7CW;cI)*u&2T2tKP1!f;;f)^rc$uba+}%l z7P{teA+k+#u>G<-{XYNz0RR6308mQ<1Qe63K?$?)KE(nByjZ7+v6H$%ACtR83V(v` z4z<{_D#Q*@8!2)N()I%win54^3rNbc|GuPFH*y16IaLQOL(b#OaAw5&hZ8NFBbJJA zwsHMC&vh_EJSFU~aex1MT*R)UG-N3(IKvzFjFtPa{rcs;N;1wh;~Fc+Fqldzfi`Zg zwMYO^D9044J4%q`if8%`aSC{r5r2e1#bpWt&-dy#AvwZInFV){9ieht96C*-r4m*~ z*#egU1 z&m)CnQaq0|N+&u_a4s-2gBh0;YV#=%fI|6JiiH6SsL3lSh&~&p7j+m&*nis$i;PxJ zpkaD5VGSrxaS^5+g>T$avZU2t^!nEHq|t95orav^f1|SJsHB+b+H)XrVPe6V%8BSJ zoX(}0!MuYUKXT$oi*8;e{E6lAulBTWLOB4ZnBT^q7LDZ}6Mb*Oj#xymF~x+x*X%5z zCJTp!XNBe>xcOu{3sQN^=M0}f3s~&fz7cmlt&lSQC$xAWN$meKJLtCrt3F8JA)UWtd%k5q}wtvUC+l!--QziN= z3_@W6CDdHHMr0Fo;nx#|QC=o??8ykD$9V1UcYC*%njeq0(sg@TRdY^DUpB7itzv)q zIEeN*gJq$|BhOY8hSAQCn+2bxeJBBuao0?OnZ~BAdsd+!b_0TC|M5~<|6!@Q3%2*5 zS8o`dPtA~QCe-)N|0AohK%8m9%KCrs^L<>rYhn2w>~~=h{U2eu>fT2oy7*ArKa-$A z6$CP%PrQ?yL?nNP?*;k}0`CsBcu|Dd0qP({Zb8yM07KCh5pe-YS@zqP)apiVAS{1UEkL2*)C&e7^?2e?87(;Aw++igLlp8k~p*_nYtE z?#hJllnX<&XAxW{rNnEH86y)2HO>e_`i4=ggcdt8AnBYPZZYOs_F=Xtc+Czradm(2QERmsa zG^gfdnf`yW#URSxCwOG7Is%K*s|hQ}L`w1~Z7E_63dNJge%9GL*OS`5xwIB)O#HRU zgTMtN+*FZ6MRKc!;5wtSt8g}zW+$^2a`-HW1IycWr3^-v%fH&wLoH`@I&%ilsY$b|C}FEBbhgN#4CyY>9g$q;)S| zTrK|(Q7(q}i9r3m-uwZRp+OW0RvIBS-~s>ukCT8$8GlQ2n=lZ7@15y?V03o`#t+QI z&Lno)=9WpC=~)r7p+QI^$q)X0SD1$r8V@EHz$@*y5ACjXeRJTcvtvppBAX-c%0&*# zsECqmGe>J@54A!4rpgO+)Fw~u`x2lSkstsqOW*D zmC#~su79ZDSgh9xWq2=Cgom!@7GF{c%CrVkOOow~MkS6Oy3ry<_MmKmBTQqW3_COo z?_$Up&v4KN4SRqf!lBnj`4^)m*m~6hje3E=s}^b8kMy75CjCgmHfh?Ag09X=}ye4U4a(`gDlL|wU?5&4Kpj8vNA6-nCVlE<< z`cXv@bF@`iSn3aIed~I7)GuF^hUyakQDr6Qmb1(hkz>VD$c4ysoXEPvd0!eV#ua3D zE{GjZt9^Uvb!?Z<#?$JU$_CiQ{WAYNJ$ing=(!zE#3E{qE-t*cS$V>t3LC?{Qgf1A zuYcnJsQ|+kU_x2PfnPC#l7VT{p_Y?$Y>kR@JQd4Hn#6h?=h#PQP5?SGQG5|)SmhO4 zU1CT~bY19{$S!kgT;pLCKHOhrr;5@hXWPv6enPxP$>3Qe1Eb-A-8Ak*cpQsa4C`?8-3)#P?$r6F9rqNh1>UEiN9y`UDY*qUrp(_VQz5fug>Rq=W@c?vFu zJCe@PFGeD!5Vk9SX(d(3CN@@xYc*BbdsH8|#wIq@l2FKlR7^9qV`%Xrp*mUQSY3uG zNbfn8vRe9gOZP}wKy9DfJG!5&mdkRlH*nAGU5?zKOtV>x2X64$=B~jwuetLlC&qjX z2g*XM@0A6bT!PL<5&mr8N^s2S8B2>hrcD>zeHK+`Q9R)~z4!-{p+OY0Yf1$N2}c4{ zXnO?!0FRS-QBMZ$o;x0owY|ElS>WM6orinK~|!WqI?FhF{P3qK!t1MZ-$LCEqA*67kK3m2~`>EW}Iv z4kj5d_YG}T?{O(;4KeUm?pP-$w&r`;0NhO^_urbteN#_*2itnVbPrrqCx45Vgr^NU z!AmM7EKpNV0kZb&*nLKH@_hxq$0_zyU~XIe=yBxubp+@cfmhOp*Kq_R#}X_b^RVi5 zJl=_iofBPLdpOe#8=f)8BFSpVG|MSwO5t@TW2Vtckz+`5${dEa8In_t^h;iHDoYez zGa;2;hY@PgOEyXljWDMuk;7N-_g(MT_ubd?-*a8hb^mc+&voC=A5ReZ!|s==VAt}% z6)mVfKdL+=4PlIMbY{S&W)fS}8+{`T zImi5(QV|p7yOk>2^W_0Xefg(kK~NO#?E$v2Q=>Xs4^F?y&k!eQX`R}unV`e3-1)R{ zS!+}Mv}V3BxQ^}39Cd%>-ut|5To>I#gFLF%&BhStgID`*)%CM7)lG8Vq)oJh8Opyd z=?`m3#Nrg}HrVB{tp^?)dbC@>KYVxlS}-@pA+J|9MbWZ4eRz7mSGn&oDZ7bu>OGfZhG?rr9s^PX>>Tf#WvlhC?OkCyDQS5JKFFS~DX?eUK&D6ar@ zEu-ybt$3sIGn$`$hhT{QPcUY6_GFW6nvU*K|F&p3*0H$~v>xKhOcGOqsq=x=PA8_R zT-$iJN9ePMuFf^Pk(gX!sm6jSE!ba*I0u`&Oz6PBjPovy^|n&#z=a&zIl?8(QKDDNs!NU^A>@I>y!mfj*v}D+@0%(MQ~wXSe^hAawvwKVnG>!rQdxIINgAq-iLD=`w>$~`1egA!O@8nc|p*3S@kRHs-PwN)-4`JpI^@nuuFWGmEmt~F3o3)HFJx*dDNru z;-UNUCzf&rF8#C}G1ENgba!}PRcF?G|COrt+VJSkVmQW^%zBER!>wbr*9x2tYIO87 zU5kdc9`0Mn!v@fQi_z3TsR`P+qG#Wx<7a(uT39qsGK1h&`efe@IzR=)d7zUi;jw{Sjm|%$7LU~`DwHy%k zQkix7-p3Q9p57p(p(AQ+^(F4%1$Rb5KT6Er9(n^+q6X5U`@)DL#>INRC(eMvJG1`6 zp!J_2jIj2-xID+(^7>jl=fe-$ zb*5J`Kh3O-mOl$DiyY4e-MB|UmZ4L$@8$H^&Mj>Si;(zci_GdjnYBFJE7{BY4TG~H z9_y`xqMT`Grq&l!mVOseuQ>RrhzTg~GAW2i3EHZ1gM_=v;8)*=h-y>HCr|wiXL1TMiu~*obGRPaV&)EOCM;>5V|m!YnY}c;d}8F>GJATZ{pwxjjiDaTz_>$ z4;NZ$&1PAg?^&SvfA}>YgG52%K(!@AM0-4=g!M8?O^DVUI)vW#OonT|r_mstr5^;g zVPE(8bkyiu` z9B7{%5HQy$QLX7oUHx5`#DYkPZK3?{(LRdQ_*7AXCwQb2)KI3e@IT0nvP`81ER7+aM zb<+m1vrKu4w+;U|ud7z2oBvt$T(V1Wzw(WIsbS$ofp5Xba6P%FDLxTS$1gC{x@K0l zJ7F-3;2f&X9fWvgon@MWq|7 zBpqYqM|Fiqa+8yepAyx%AVU=EIOiKYQ6rEPcZZTJCleO#J(){d?&*2I0Qc`^@;KHP zJ)`{23j~yQ_yffcqWrQ}ibdZjd`j=+CQA2(n~ZK}??};hWs>Cug)X1XQ6C6Klc@>~ z%$q{rOxUp!c_m;;X!(yJYiiZ1aaPWBIgM50?ohfMMr2XyBS)(zD;9vg* zg^(S=wdOz*8TFqt$s$=E9BT!LQ;`2oplAvde8vW_qo^wV@SBrD}nl&j1{%8JOe=NXHq2 zkA?!cI16w95unE*75`orLLiXfN38hYaUX$~aY&_~`W^q#9~b_AS^x>yMl%tqhy{Uw R#TaEEkhVj#hWwMyzX1PD9pnH2 From dc3a127f35ff2192c40a75182e36717ca977913e Mon Sep 17 00:00:00 2001 From: bendodge-xwis <32881391+WyattBest@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:20:33 -0600 Subject: [PATCH 4/5] Update [custom].[PS_updAction].sql Fixes #42 --- SQL/[custom].[PS_updAction].sql | 304 +++++++++++++++++++++----------- 1 file changed, 203 insertions(+), 101 deletions(-) diff --git a/SQL/[custom].[PS_updAction].sql b/SQL/[custom].[PS_updAction].sql index 5127999..1c9ee6c 100644 --- a/SQL/[custom].[PS_updAction].sql +++ b/SQL/[custom].[PS_updAction].sql @@ -8,122 +8,224 @@ GO SET QUOTED_IDENTIFIER ON GO - -- ============================================= -- Author: Wyatt Best -- Create date: 2016-12-07 --- Description: Inserts or updates a scheduled action. +-- Description: Inserts or updates a scheduled action. For simplicity, SCHEDULED_TIME is not supported. -- --- Dependencies: MCNY_SP_insert_action --- --- 2017-10-11 Wyatt Best: Changed WAIVED_REASON from SLATE to ADMIS. --- Fixed the 'updated waived after inserting new' section. --- 2019-10-15 Wyatt Best: Renamed and moved to [custom] schema. +-- 2017-10-11 Wyatt Best: Changed WAIVED_REASON from SLATE to ADMIS. +-- Fixed the 'updated waived after inserting new' section. +-- 2019-10-15 Wyatt Best: Renamed and moved to [custom] schema. +-- 2021-06-14 Wyatt Best: Rewrite to remove dependency on MCNY_SP_insert_action. New optional parameters @Responsible and @CompletedDate. -- ============================================= - -CREATE PROCEDURE [custom].[PS_updAction] - @PCID nvarchar(10) - ,@Opid nvarchar(8) - ,@action_id nvarchar(8) - ,@action_name nvarchar(50) - ,@completed nvarchar(1) --Y, N, W (waived) - ,@sched_date datetime --Slate checklist item added date - ,@year nvarchar(4) - ,@term nvarchar(10) - ,@session nvarchar(10) - +ALTER PROCEDURE [custom].[PS_updAction] @PCID NVARCHAR(10) + ,@Opid NVARCHAR(8) + ,@ActionID NVARCHAR(8) + ,@ActionName NVARCHAR(50) + ,@Responsible NVARCHAR(10) = NULL + ,@Completed NVARCHAR(1) --Y, N, W (waived) + ,@CompletedDate DATETIME = NULL + ,@ScheduledDate DATETIME --Slate checklist item added date + ,@AcademicYear NVARCHAR(4) + ,@AcademicTerm NVARCHAR(10) + ,@AcademicSession NVARCHAR(10) AS BEGIN SET NOCOUNT ON; - BEGIN TRANSACTION - - --DECLARE @PersonId int = dbo.fnGetPersonId(@PCID) - DECLARE @getdate datetime = getdate() - DECLARE @Today datetime = dbo.fnMakeDate(@getdate) - DECLARE @Now datetime = dbo.fnMakeTime(@getdate) - DECLARE @Waived nvarchar(1) = 'N' - DECLARE @Actionschedule_id int - - --Just in case - SET @sched_date = dbo.fnMakeDate(@sched_date) - IF @completed = 'W' - BEGIN - SET @Waived = 'Y' - SET @completed = 'N' - END + DECLARE @getdate DATETIME = GETDATE() + DECLARE @Today DATETIME = dbo.fnMakeDate(@getdate) + ,@Now DATETIME = dbo.fnMakeTime(@getdate) + ,@Waived NVARCHAR(1) = 'N' + ,@Actionschedule_Id INT - --Find closest existing action - SELECT TOP 1 @Actionschedule_id = ACTIONSCHEDULE_ID - FROM ACTIONSCHEDULE - WHERE PEOPLE_ORG_CODE_ID = @PCID - AND ACTION_ID = @action_id - AND ACTION_NAME = @action_name - AND ACADEMIC_YEAR = @year - AND ACADEMIC_TERM = @term - AND ACADEMIC_SESSION = @session - ORDER BY ACTIONSCHEDULE_ID DESC + IF @Completed = 'W' + BEGIN + SET @Waived = 'Y' + SET @Completed = 'N' + END - --If a match found - IF (@Actionschedule_id IS NOT NULL) - --Update various columns - BEGIN - --Update completed - IF NOT EXISTS (SELECT * FROM ACTIONSCHEDULE WHERE ACTIONSCHEDULE_ID = @Actionschedule_id AND COMPLETED = @completed) - UPDATE ACTIONSCHEDULE - SET COMPLETED = @completed - ,EXECUTION_DATE = @Today - ,REVISION_OPID = @Opid - ,REVISION_DATE = @Today - ,REVISION_TIME = @Now - WHERE ACTIONSCHEDULE_ID = @Actionschedule_id - --Update waived - IF NOT EXISTS (SELECT * FROM ACTIONSCHEDULE WHERE ACTIONSCHEDULE_ID = @Actionschedule_id AND WAIVED = @Waived) - UPDATE ACTIONSCHEDULE - SET WAIVED = @Waived - ,WAIVED_REASON = 'ADMIS' - ,REVISION_OPID = @Opid - ,REVISION_DATE = @Today - ,REVISION_TIME = @Now - WHERE ACTIONSCHEDULE_ID = @Actionschedule_id - --Update scheduled_date and time - IF NOT EXISTS (SELECT * FROM ACTIONSCHEDULE WHERE ACTIONSCHEDULE_ID = @Actionschedule_id AND SCHEDULED_DATE = @sched_date) - UPDATE ACTIONSCHEDULE - SET SCHEDULED_DATE = @sched_date - ,SCHEDULED_TIME = '1900-01-01 00:00:01.000' - ,REVISION_OPID = @Opid - ,REVISION_DATE = @Today - ,REVISION_TIME = @Now - WHERE ACTIONSCHEDULE_ID = @Actionschedule_id - END - --If no match found - ELSE - BEGIN - EXEC dbo.MCNY_SP_insert_action @action_id, @PCID, @sched_date, NULL, @Opid, NULL, NULL, 'Y', NULL, @completed, @year, @term, @session, @action_name, 'N' + DECLARE @WaivedReason NVARCHAR(6) = CASE + WHEN @Waived = 'Y' + THEN 'ADMIS' + ELSE NULL END - --Update waived - SELECT TOP 1 @Actionschedule_id = ACTIONSCHEDULE_ID - FROM ACTIONSCHEDULE - WHERE PEOPLE_ORG_CODE_ID = @PCID - AND ACTION_ID = @action_id - AND ACTION_NAME = @action_name - AND ACADEMIC_YEAR = @year - AND ACADEMIC_TERM = @term - AND ACADEMIC_SESSION = @session - ORDER BY ACTIONSCHEDULE_ID DESC - IF NOT EXISTS (SELECT * FROM ACTIONSCHEDULE WHERE ACTIONSCHEDULE_ID = @Actionschedule_id AND WAIVED = @Waived) - UPDATE ACTIONSCHEDULE - SET WAIVED = @Waived - ,WAIVED_REASON = 'ADMIS' - ,REVISION_OPID = @Opid - ,REVISION_DATE = @Today - ,REVISION_TIME = @Now - WHERE ACTIONSCHEDULE_ID = @Actionschedule_id - COMMIT -END + SET @ScheduledDate = dbo.fnMakeDate(@ScheduledDate) + SET @CompletedDate = COALESCE(dbo.fnMakeDate(@CompletedDate), @Today) + SET @Responsible = COALESCE(@Responsible, @PCID) + --Find existing action by PCID, Action ID, Action Name, YTS, and CREATE_OPID + SELECT TOP 1 @Actionschedule_Id = ACTIONSCHEDULE_ID + FROM ACTIONSCHEDULE + WHERE PEOPLE_ORG_CODE_ID = @PCID + AND ACTION_ID = @ActionID + AND ACTION_NAME = @ActionName + AND ACADEMIC_YEAR = @AcademicYear + AND ACADEMIC_TERM = @AcademicTerm + AND ACADEMIC_SESSION = @AcademicSession + AND CREATE_OPID = @Opid + ORDER BY ACTIONSCHEDULE_ID DESC + --If match found, update various columns + IF (@Actionschedule_Id IS NOT NULL) + UPDATE ACTIONSCHEDULE + SET SCHEDULED_DATE = @ScheduledDate + ,SCHEDULED_TIME = '1900-01-01 00:00:01.000' + ,RESP_STAFF = @Responsible + ,EXECUTION_DATE = CASE + WHEN COMPLETED <> 'Y' + AND @Completed = 'Y' + THEN @Today + ELSE NULL + END + ,COMPLETED = @Completed + ,COMPLETED_BY = CASE + WHEN COMPLETED_BY IS NULL + AND @Completed = 'Y' + THEN RESP_STAFF + ELSE COMPLETED_BY + END + ,WAIVED = @Waived + ,WAIVED_REASON = @WaivedReason + ,REVISION_OPID = @Opid + ,REVISION_DATE = @Today + ,REVISION_TIME = @Now + WHERE ACTIONSCHEDULE_ID = @Actionschedule_Id + AND EXISTS ( + SELECT SCHEDULED_DATE + ,SCHEDULED_TIME + ,RESP_STAFF + ,CASE + WHEN COMPLETED <> 'Y' + AND @Completed = 'Y' + THEN @Today + ELSE NULL + END [EXECUTION_DATE] + ,COMPLETED + ,WAIVED + ,WAIVED_REASON + + EXCEPT + + SELECT @ScheduledDate + ,'1900-01-01 00:00:01.000' + ,@Responsible + ,CASE + WHEN COMPLETED <> 'Y' + AND @Completed = 'Y' + THEN @Today + ELSE NULL + END [EXECUTION_DATE] + ,@Completed + ,@Waived + ,@WaivedReason + ) + ELSE + BEGIN + --Insert the scheduled action + INSERT INTO ACTIONSCHEDULE ( + ACTION_ID + ,PEOPLE_ORG_CODE + ,PEOPLE_ORG_ID + ,PEOPLE_ORG_CODE_ID + ,REQUEST_DATE + ,REQUEST_TIME + ,SCHEDULED_DATE + ,EXECUTION_DATE + ,CREATE_DATE + ,CREATE_TIME + ,CREATE_OPID + ,CREATE_TERMINAL + ,REVISION_DATE + ,REVISION_TIME + ,REVISION_OPID + ,REVISION_TERMINAL + ,ABT_JOIN + ,ACTION_NAME + ,OFFICE + ,[TYPE] + ,RESP_STAFF + ,COMPLETED_BY + ,[REQUIRED] + ,[PRIORITY] + ,RATING + ,RESPONSE + ,CONTACT + ,SCHEDULED_TIME + ,NOTE + ,UNIQUE_KEY + ,COMPLETED + ,WAIVED + ,WAIVED_REASON + ,CANCELED + ,CANCELED_REASON + ,NUM_OF_REMINDERS + ,ACADEMIC_YEAR + ,ACADEMIC_TERM + ,ACADEMIC_SESSION + ,RULE_ID + ,SEQ_NUM + ,DURATION + ,DOCUMENT + ,Instruction + ) + SELECT @ActionID [ACTION_ID] + ,'P' [PEOPLE_ORG_CODE] + ,RIGHT(@PCID, 9) [PEOPLE_ORG_ID] + ,@PCID [PEOPLE_ORG_CODE_ID] + ,@Today [REQUEST_DATE] + ,@Now [REQUEST_TIME] + ,@ScheduledDate [SCHEDULED_DATE] + ,CASE + WHEN @Completed = 'Y' + THEN @Today + ELSE NULL + END AS [EXECUTION_DATE] + ,@Today [CREATE_DATE] + ,@Now [CREATE_TIME] + ,@Opid [CREATE_OPID] + ,'0001' [CREATE_TERMINAL] + ,@Today [REVISION_DATE] + ,@Now [REVISION_TIME] + ,@Opid [REVISION_OPID] + ,'0001' [REVISION_TERMINAL] + ,'*' [ABT_JOIN] + ,coalesce(@ActionName, A.ACTION_NAME) [ACTION_NAME] + ,OFFICE + ,[TYPE] + ,@Responsible [RESP_STAFF] + ,CASE + WHEN @Completed = 'Y' + THEN RESP_STAFF + ELSE NULL + END AS [COMPLETED_BY] + ,[REQUIRED] + ,[PRIORITY] + ,NULL [RATING] + ,NULL [RESPONSE] + ,NULL [CONTACT] + ,'1900-01-01 00:00:01.000' [SCHEDULED_TIME] + ,[NOTE] + ,@ActionId + convert(NVARCHAR(4), datepart(yy, @getdate)) + convert(NVARCHAR(2), datepart(mm, @getdate)) + convert(NVARCHAR(2), datepart(dd, @getdate)) + convert(NVARCHAR(2), datepart(hh, @getdate)) + convert(NVARCHAR(2), datepart(mi, @getdate)) + convert(NVARCHAR(4), datepart(ms, @getdate)) [UNIQUE_KEY] + ,@Completed [COMPLETED] + ,@Waived [WAIVED] + ,@WaivedReason [WAIVED_REASON] + ,NULL [CANCELED] + ,NULL [CANCELED_REASON] + ,0 [NUM_OF_REMINDERS] + ,@AcademicYear [ACADEMIC_YEAR] + ,@AcademicTerm [ACADEMIC_TERM] + ,@AcademicSession [ACADEMIC_SESSION] + ,0 [RULE_ID] + ,0 [SEQ_NUM] + ,'' [Duration] + ,NULL [DOCUMENT] + ,[Instruction] + FROM [ACTION] A + WHERE ACTION_ID = @ActionID + END +END GO From 0df296a04c76ced4a653a8eaada34facd9fcd6a6 Mon Sep 17 00:00:00 2001 From: bendodge-xwis <32881391+WyattBest@users.noreply.github.com> Date: Mon, 21 Jun 2021 20:05:10 -0600 Subject: [PATCH 5/5] Bugfixes and polish for scheduled actions Fix bug that caused cartesian product of actions_list * apps_list to be written. Rename PS_updAction parameters for clarity. Fix datatype bug with autolearning. New example Slate objects. Close #40 --- SQL/[custom].[PS_updAction].sql | 10 +++++----- Sample Slate Objects.txt | 5 ++++- ps_core.py | 9 +++++---- ps_powercampus.py | 7 ++++--- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/SQL/[custom].[PS_updAction].sql b/SQL/[custom].[PS_updAction].sql index 1c9ee6c..cc63f14 100644 --- a/SQL/[custom].[PS_updAction].sql +++ b/SQL/[custom].[PS_updAction].sql @@ -16,16 +16,16 @@ GO -- 2017-10-11 Wyatt Best: Changed WAIVED_REASON from SLATE to ADMIS. -- Fixed the 'updated waived after inserting new' section. -- 2019-10-15 Wyatt Best: Renamed and moved to [custom] schema. --- 2021-06-14 Wyatt Best: Rewrite to remove dependency on MCNY_SP_insert_action. New optional parameters @Responsible and @CompletedDate. +-- 2021-06-21 Wyatt Best: Rewrite to remove dependency on MCNY_SP_insert_action. New parameters @Responsible (optional) and @CompletedDate. -- ============================================= -ALTER PROCEDURE [custom].[PS_updAction] @PCID NVARCHAR(10) +CREATE PROCEDURE [custom].[PS_updAction] @PCID NVARCHAR(10) ,@Opid NVARCHAR(8) ,@ActionID NVARCHAR(8) ,@ActionName NVARCHAR(50) ,@Responsible NVARCHAR(10) = NULL + ,@ScheduledDate DATE --Slate effective date or application creation date ,@Completed NVARCHAR(1) --Y, N, W (waived) - ,@CompletedDate DATETIME = NULL - ,@ScheduledDate DATETIME --Slate checklist item added date + ,@CompletedDate DATE --Slate activity date ,@AcademicYear NVARCHAR(4) ,@AcademicTerm NVARCHAR(10) ,@AcademicSession NVARCHAR(10) @@ -211,7 +211,7 @@ BEGIN ,@Completed [COMPLETED] ,@Waived [WAIVED] ,@WaivedReason [WAIVED_REASON] - ,NULL [CANCELED] + ,'N' [CANCELED] ,NULL [CANCELED_REASON] ,0 [NUM_OF_REMINDERS] ,@AcademicYear [ACADEMIC_YEAR] diff --git a/Sample Slate Objects.txt b/Sample Slate Objects.txt index 78b6170..9bb2f2d 100644 --- a/Sample Slate Objects.txt +++ b/Sample Slate Objects.txt @@ -2,13 +2,16 @@ Note that these queries leave much to be desired at this time. Phone number types are static instead of dynamic and many new fields are not being exported. +PowerSlate CJ Library: e6f215a4-099c-5c1a-2668-2f2337d103bf@mcn +Contains some MCNY-specific translations. + PowerSlate 24 hr: ab57a5a3-416f-3212-491d-61556409c3d7@mcn Can use for 15-minute sync if you enable Application Update Queue. PowerSlate HTTP: 14cefa84-b09e-7310-2cb0-66687ec62be1@mcn Used for HTTP sync. Accepts a person GUID parameter 'pid'. -PowerSlate Scheduled Actions: ace96b41-82e0-1bc9-4d7b-306e29f1b95f@mcn +PowerSlate Scheduled Actions: 37481add-13be-9dd0-88de-6956376eb032@mcn Custom SQL query for Scheduled Actions. Might be replaceable with Configurable Joins. Contains some MCNY-specific ACTION_ID's. diff --git a/ps_core.py b/ps_core.py index 126ac2d..4b5c09a 100644 --- a/ps_core.py +++ b/ps_core.py @@ -196,7 +196,7 @@ def learn_actions(actions_list): # Sanity check against PowerCampus for action_id in learned_actions: action_def = ps_powercampus.get_action_definition(action_id) - if len(action_def) < 1: + if action_def is None: learned_actions.remove(action_id) admissions_action_codes += learned_actions @@ -310,11 +310,12 @@ def main_sync(pid=None): # Update PowerCampus Scheduled Actions if CONFIG['scheduled_actions']['enabled'] == True: - for action in actions_list: + app_actions = [k for k in actions_list if k['aid'] == v['aid']] + + for action in app_actions: ps_powercampus.update_action( action, pcid, academic_year, academic_term, academic_session) - - app_actions = [k for k in actions_list if k['aid'] == v['aid']] + ps_powercampus.cleanup_actions( CONFIG['scheduled_actions']['admissions_action_codes'], app_actions, pcid, academic_year, academic_term, academic_session) diff --git a/ps_powercampus.py b/ps_powercampus.py index 4a37268..047b82b 100644 --- a/ps_powercampus.py +++ b/ps_powercampus.py @@ -390,14 +390,15 @@ def update_action(action, pcid, academic_year, academic_term, academic_session): academic_session -- string """ - CURSOR.execute('EXEC [custom].[PS_updAction] ?, ?, ?, ?, ?, ?, ?, ?, ?', + CURSOR.execute('EXEC [custom].[PS_updAction] ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?', pcid, 'SLATE', action['action_id'], action['item'], + pcid, + action['scheduled_date'], action['completed'], - # Only the date portion is actually used. - action['create_datetime'], + action['completed_date'], academic_year, academic_term, academic_session)