|
1 | 1 | using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Diagnostics.Metrics; |
| 4 | +using System.Reflection.Emit; |
2 | 5 | using System.Threading; |
3 | 6 | using System.Threading.Tasks; |
| 7 | +using System.Xml.Linq; |
4 | 8 | using CronScheduler.Extensions.Scheduler; |
| 9 | +using Mms.Database; |
| 10 | +using PetaPoco; |
| 11 | +using static SQLite.SQLite3; |
| 12 | +using WildApricot; |
| 13 | +using static System.Runtime.InteropServices.JavaScript.JSType; |
| 14 | +using System.Security.Policy; |
| 15 | +using Serilog; |
5 | 16 |
|
6 | 17 | namespace Mms.Api.Jobs |
7 | 18 | { |
8 | 19 | public class PullMembersFromWildApricot : IScheduledJob |
9 | 20 | { |
10 | 21 | public string Name { get; } = nameof(PullMembersFromWildApricot); |
11 | 22 |
|
12 | | - public Task ExecuteAsync(CancellationToken cancellationToken) |
| 23 | + public async Task ExecuteAsync(CancellationToken cancellationToken) |
13 | 24 | { |
14 | | - throw new NotImplementedException(); |
| 25 | + Log.Information("Beginning Wild Apricot Sync"); |
| 26 | + |
| 27 | + var wildApricot = new WildApricotClient(); |
| 28 | + |
| 29 | + var contactsCount = await wildApricot.GetContactsListAsync( |
| 30 | + accountId: wildApricot.accountId, |
| 31 | + //count: true, |
| 32 | + filter: "Archived eq false" |
| 33 | + ); |
| 34 | + |
| 35 | + ContactsResponse contacts = null; |
| 36 | + |
| 37 | + var attempts = 0; |
| 38 | + |
| 39 | + while (attempts < 10 && string.IsNullOrWhiteSpace(contacts?.Processed) && contacts?.ProcessingState != ContactsAsyncResponseProcessingState.Complete) { |
| 40 | + await Task.Delay(5000); |
| 41 | + |
| 42 | + contacts = await wildApricot.GetContactsListAsync( |
| 43 | + accountId: wildApricot.accountId, |
| 44 | + resultId: contactsCount.ResultId |
| 45 | + ); |
| 46 | + |
| 47 | + attempts += 1; |
| 48 | + } |
| 49 | + |
| 50 | + if ((string.IsNullOrWhiteSpace(contacts?.Processed) && contacts?.ProcessingState != ContactsAsyncResponseProcessingState.Complete) || contacts?.Contacts?.Count < 101) |
| 51 | + throw new Exception("Wild Apricot did not return complete members list!"); |
| 52 | + |
| 53 | + using var accessDb = new AccessControlDatabase(); |
| 54 | + using var fundingDb = new AreaFundingDatabase(); |
| 55 | + |
| 56 | + Log.Information($"Contacts Returned: {contacts.Contacts.Count}"); |
| 57 | + |
| 58 | + var totalFunding = new fund(); |
| 59 | + |
| 60 | + foreach (var contact in contacts.Contacts) { |
| 61 | + // For Keys |
| 62 | + string key1 = null; |
| 63 | + string key2 = null; |
| 64 | + string type = null; |
| 65 | + DateTime? joined = null; |
| 66 | + DateTime? renewal = null; |
| 67 | + var id = contact.Id ?? -1; |
| 68 | + var key3 = $"{id}#"; |
| 69 | + var fullname = $"{contact.FirstName} {contact.LastName}"; |
| 70 | + var apricot_admin = contact.IsAccountAdministrator ?? false; |
| 71 | + |
| 72 | + // For Funding |
| 73 | + var active = false; |
| 74 | + var specialPurpose = false; |
| 75 | + var amount = 0m; |
| 76 | + |
| 77 | + // If a member is currently pending renewal (during last 10 days of month, for instance), we count them as active |
| 78 | + switch (contact.Status ?? ContactStatus.Lapsed) { |
| 79 | + case ContactStatus.Active: |
| 80 | + case ContactStatus.PendingRenewal: |
| 81 | + active = true; |
| 82 | + break; |
| 83 | + } |
| 84 | + |
| 85 | + // If an account is active, and set to the type "Special Purpose Account" |
| 86 | + // we do not count it towards the population or area funding. |
| 87 | + // We do put a key in the access control system for it. |
| 88 | + // Expiration date is overridden below. |
| 89 | + if (contact.MembershipLevel?.Name == "Special Purpose Account") { |
| 90 | + active = false; |
| 91 | + specialPurpose = true; |
| 92 | + } |
| 93 | + |
| 94 | + foreach (var field in contact.FieldValues) { |
| 95 | + switch (field.FieldName) { |
| 96 | + case "Key Fob Code": |
| 97 | + key1 = field.Value?.ToString().ToUpperInvariant().Trim(); |
| 98 | + break; |
| 99 | + case "PIN Code": |
| 100 | + key2 = field.Value?.ToString().ToUpperInvariant().Trim(); |
| 101 | + break; |
| 102 | + case "Member since": |
| 103 | + joined = DateTime.Parse(field.Value?.ToString() ?? "2000-01-01"); |
| 104 | + break; |
| 105 | + case "Renewal due": |
| 106 | + renewal = DateTime.Parse(field.Value?.ToString() ?? "2000-01-01"); |
| 107 | + break; |
| 108 | + case "Member role": |
| 109 | + switch (field.Value?.ToString() ?? "") { |
| 110 | + case "Bundle Administrator": |
| 111 | + case "Bundle administrator": |
| 112 | + case "Individual": |
| 113 | + case "": |
| 114 | + type = "General"; |
| 115 | + |
| 116 | + if (active) { |
| 117 | + totalFunding.general += 1; |
| 118 | + amount = 1.5m; |
| 119 | + } |
| 120 | + break; |
| 121 | + case "Bundle Member": |
| 122 | + case "Bundle member": |
| 123 | + type = "Family"; |
| 124 | + |
| 125 | + if (active) { |
| 126 | + totalFunding.family += 1; |
| 127 | + amount = 0.38m; |
| 128 | + } |
| 129 | + break; |
| 130 | + } |
| 131 | + break; |
| 132 | + case "Suspended member": |
| 133 | + case "Archived": |
| 134 | + // If the BOD suspends someone before they expire, we immediately disable their key |
| 135 | + if (field.Value?.ToString() == "1") { |
| 136 | + active = false; |
| 137 | + var sql = @" |
| 138 | + UPDATE |
| 139 | + member |
| 140 | + SET |
| 141 | + expires = '2000-01-01' |
| 142 | + WHERE |
| 143 | + member_id = @0;"; |
| 144 | + |
| 145 | + accessDb.Execute(sql, id); |
| 146 | + } |
| 147 | + break; |
| 148 | + case "$1.50/Month Area #1": |
| 149 | + case "$1.50/Month Area #2": |
| 150 | + case "$1.50/Month Area #3": |
| 151 | + case "$1.50/Month Area #4": |
| 152 | + case "$1.50/Month Area #5": |
| 153 | + if (active) { |
| 154 | + if (field.Value == null) |
| 155 | + Log.Error($"Null Area Selection! - {fullname} - {field.FieldName} - {field.Value}"); |
| 156 | + else { |
| 157 | + TabulateFunds(totalFunding, field.FieldName, amount); |
| 158 | + totalFunding.total += amount; |
| 159 | + } |
| 160 | + } |
| 161 | + break; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + if (active) |
| 166 | + totalFunding.members += 1; |
| 167 | + |
| 168 | + if (specialPurpose) { |
| 169 | + // Always set special purpose accounts to expire one month into the future. |
| 170 | + renewal = (new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day)).AddMonths(1); |
| 171 | + } |
| 172 | + |
| 173 | + // Record keys into access database |
| 174 | + if (active || specialPurpose) { |
| 175 | + if (!string.IsNullOrEmpty(key1)) { |
| 176 | + var sql2 = @$" |
| 177 | + REPLACE INTO |
| 178 | + keycode(keycode_id, member_id, updated) |
| 179 | + VALUES |
| 180 | + (@0, @1, NOW()); "; |
| 181 | + |
| 182 | + accessDb.Execute(sql2, key1, id); |
| 183 | + } |
| 184 | + |
| 185 | + if (!string.IsNullOrEmpty(key2)) { |
| 186 | + var sql3 = @$" |
| 187 | + REPLACE INTO |
| 188 | + keycode(keycode_id, member_id, updated) |
| 189 | + VALUES |
| 190 | + (@0, @1, NOW()); "; |
| 191 | + |
| 192 | + accessDb.Execute(sql3, key2, id); |
| 193 | + } |
| 194 | + |
| 195 | + var sql4 = @$" |
| 196 | + REPLACE INTO |
| 197 | + keycode(keycode_id, member_id, updated) |
| 198 | + VALUES |
| 199 | + (@0, @1, NOW()); "; |
| 200 | + |
| 201 | + accessDb.Execute(sql4, key3, id); |
| 202 | + |
| 203 | + var sql5 = @" |
| 204 | + REPLACE INTO |
| 205 | + member(member_id, name, type, apricot_admin, joined, expires, updated) |
| 206 | + VALUES |
| 207 | + (@0, @1, @2, @3, @4, @5, NOW()); "; |
| 208 | + |
| 209 | + accessDb.Execute(sql5, id, fullname, type, apricot_admin, joined, renewal); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + totalFunding.month = DateTime.Now; |
| 214 | + |
| 215 | + fundingDb.Insert(totalFunding); |
| 216 | + } |
| 217 | + |
| 218 | + private void TabulateFunds(fund total, string area, decimal amount) |
| 219 | + { |
| 220 | + switch (area) { |
| 221 | + case "* Not Allocated *": |
| 222 | + total.building_purchase += amount; |
| 223 | + break; |
| 224 | + case "3D Printers": |
| 225 | + total.threed_printer += amount; |
| 226 | + break; |
| 227 | + case "Bicycle Repair": |
| 228 | + total.bicycle_repair += amount; |
| 229 | + break; |
| 230 | + case "Blacksmith and Forge": |
| 231 | + total.forge += amount; |
| 232 | + break; |
| 233 | + case "Casting": |
| 234 | + total.casting += amount; |
| 235 | + break; |
| 236 | + case "Ceramic Studio": |
| 237 | + total.ceramic += amount; |
| 238 | + break; |
| 239 | + case "CNC Room": |
| 240 | + total.cnc += amount; |
| 241 | + break; |
| 242 | + case "Cosplay": |
| 243 | + total.cosplay += amount; |
| 244 | + break; |
| 245 | + case "Craft Lab": |
| 246 | + total.craft += amount; |
| 247 | + break; |
| 248 | + case "Dalek Asylum": |
| 249 | + total.dalek += amount; |
| 250 | + break; |
| 251 | + case "Electronic Lab": |
| 252 | + total.electronic += amount; |
| 253 | + break; |
| 254 | + case "Fine Art Printing": |
| 255 | + total.print += amount; |
| 256 | + break; |
| 257 | + case "Finishing": |
| 258 | + total.finishing += amount; |
| 259 | + break; |
| 260 | + case "Ham Radio": |
| 261 | + total.ham_radio += amount; |
| 262 | + break; |
| 263 | + case "Jewelry and Non Ferrous Metals": |
| 264 | + total.jewelry += amount; |
| 265 | + break; |
| 266 | + case "Lampworking": |
| 267 | + total.lampworking += amount; |
| 268 | + break; |
| 269 | + case "Laser Cutters": |
| 270 | + total.laser += amount; |
| 271 | + break; |
| 272 | + case "Leather Working": |
| 273 | + total.leather += amount; |
| 274 | + break; |
| 275 | + case "Long Arm Quilting": |
| 276 | + total.long_arm += amount; |
| 277 | + break; |
| 278 | + case "Community Outreach": |
| 279 | + total.makerfaire += amount; |
| 280 | + break; |
| 281 | + case "Metal Shop": |
| 282 | + total.metal += amount; |
| 283 | + break; |
| 284 | + case "Neon": |
| 285 | + total.neon += amount; |
| 286 | + break; |
| 287 | + case "Paint Room": |
| 288 | + total.paint += amount; |
| 289 | + break; |
| 290 | + case "Stained Glass": |
| 291 | + total.stained_glass += amount; |
| 292 | + break; |
| 293 | + case "Tiger Lily Sculpture Gang": |
| 294 | + total.tiger_lily += amount; |
| 295 | + break; |
| 296 | + case "Vacuum Former": |
| 297 | + total.vacuum += amount; |
| 298 | + break; |
| 299 | + case "Welders": |
| 300 | + total.welding += amount; |
| 301 | + break; |
| 302 | + case "Wood Shop": |
| 303 | + total.wood += amount; |
| 304 | + break; |
| 305 | + } |
15 | 306 | } |
16 | 307 | } |
17 | 308 | } |
0 commit comments