diff --git a/WNPRC_Compliance/module.properties b/WNPRC_Compliance/module.properties index 97cb3c7ae..0c6656025 100644 --- a/WNPRC_Compliance/module.properties +++ b/WNPRC_Compliance/module.properties @@ -4,6 +4,6 @@ License: Apache 2.0 LicenseURL: http://www.apache.org/licenses/LICENSE-2.0 ModuleClass: org.labkey.wnprc_compliance.WNPRC_ComplianceModule Name: WNPRC_Compliance -SchemaVersion: 21.001 +SchemaVersion: 25.001 SupportedDatabases: pgsql ManageVersion: false diff --git a/WNPRC_Compliance/resources/queries/wnprc_compliance/MostRecentAccessReportSummary.sql b/WNPRC_Compliance/resources/queries/wnprc_compliance/MostRecentAccessReportSummary.sql index 5fb8f372b..4001496d6 100644 --- a/WNPRC_Compliance/resources/queries/wnprc_compliance/MostRecentAccessReportSummary.sql +++ b/WNPRC_Compliance/resources/queries/wnprc_compliance/MostRecentAccessReportSummary.sql @@ -11,13 +11,8 @@ personsList.isArchived, card_info.first_name, card_info.last_name, card_info.middle_name, -card_info.department, -card_info.employee_number, -card_info.info2, -card_info.info3, -card_info.info5, -access_info.areas, +access_info.access_levels, persons_to_cards.personid @@ -25,19 +20,14 @@ FROM ( SELECT report_id, card_id, - GROUP_CONCAT(display_area, ';') as areas + GROUP_CONCAT(access_level, ';') as access_levels FROM ( SELECT reports.report_id, report_data.card_id, - report_data.enabled, - report_data.area, - CASE - WHEN report_data.enabled IS FALSE THEN CAST(COALESCE(report_data.area, '') || ' (disabled)' as VARCHAR) - ELSE report_data.area - END as display_area + report_data.access_level, FROM wnprc_compliance.access_reports reports, wnprc_compliance.access_report_data report_data diff --git a/WNPRC_Compliance/resources/queries/wnprc_compliance/personsList.sql b/WNPRC_Compliance/resources/queries/wnprc_compliance/personsList.sql index c15b87e36..7f8fa893c 100644 --- a/WNPRC_Compliance/resources/queries/wnprc_compliance/personsList.sql +++ b/WNPRC_Compliance/resources/queries/wnprc_compliance/personsList.sql @@ -4,7 +4,6 @@ last_name, first_name, middle_name, date_of_birth, -cardInfo.employee_number, notes, tbResults.lastClearance as lastTbClearance, measlesResults.lastClearance as measlesClearance, @@ -51,16 +50,3 @@ LEFT JOIN ( ON measlesResults.person_id = persons.personid ---Adding employee_number from card_info table. Need to select the latest uploaded record to the card_info table ---Use person_to_cards table to link the personid to the card_id. -LEFT JOIN wnprc_compliance.persons_to_cards pers_to_card ON (persons.personid = pers_to_card.personid) -LEFT JOIN - ( - SELECT card_info.employee_number, card_info.card_id, MAX(card_info.created) - FROM wnprc_compliance.card_info - GROUP BY card_info.card_id, card_info.employee_number - ) cardInfo -ON (pers_to_card.cardid = cardInfo.card_id) - -ORDER BY last_name ASC - diff --git a/WNPRC_Compliance/resources/queries/wnprc_compliance/searchResults.sql b/WNPRC_Compliance/resources/queries/wnprc_compliance/searchResults.sql index ace514a3b..0f1a68bc4 100644 --- a/WNPRC_Compliance/resources/queries/wnprc_compliance/searchResults.sql +++ b/WNPRC_Compliance/resources/queries/wnprc_compliance/searchResults.sql @@ -7,9 +7,9 @@ id, first_name, middle_name, last_name, -display, +COALESCE(display, '') as display, LCASE(display) as displayLcase, -notes, +COALESCE(notes, '') as notes, "type" FROM ( @@ -33,7 +33,7 @@ FROM ( SELECT card_id as id, first_name, - middle_name, + COALESCE(middle_name, ''), last_name, COALESCE(first_name, '') || ' ' || COALESCE(middle_name, '') || ' ' || COALESCE(last_name, '') || ' (' || card_id || ')' as display, COALESCE(department || ';', '') || COALESCE(info2 || ';', '') || COALESCE(info3 || ';', '') as notes, @@ -48,7 +48,7 @@ FROM ( SELECT personid as id, first_name, - middle_name, + COALESCE(middle_name, ''), last_name, COALESCE(first_name, '') || ' ' || COALESCE(middle_name, '') || ' ' || COALESCE(last_name, '') as display, notes, diff --git a/WNPRC_Compliance/resources/queries/wnprc_compliance/unidentifiedCards.sql b/WNPRC_Compliance/resources/queries/wnprc_compliance/unidentifiedCards.sql index 21bd8607a..a237bd342 100644 --- a/WNPRC_Compliance/resources/queries/wnprc_compliance/unidentifiedCards.sql +++ b/WNPRC_Compliance/resources/queries/wnprc_compliance/unidentifiedCards.sql @@ -3,11 +3,10 @@ cards.card_id, last_name, first_name, middle_name, -department, -employee_number, -info2, -info3, -info5 +card_info.card_type, +to_char(card_info.date_issued , 'yyyy-MM-dd') AS date_issued, +to_char(card_info.date_expire, 'yyyy-MM-dd') AS date_expire, +card_info.issue_code FROM ( SELECT @@ -18,12 +17,16 @@ FROM ( SELECT unknown_cards.card_id, card_info.report_id, - card_info.report_id.date + card_info.report_id.date, + card_info.card_type, + card_info.date_issued, + card_info.date_expire, + card_info.issue_code FROM ( SELECT cards.card_id, - persons_to_cards.personid + persons_to_cards.personid, FROM ( SELECT card_id diff --git a/WNPRC_Compliance/resources/schemas/dbscripts/postgresql/wnprc_compliance-25.000-25.001.sql b/WNPRC_Compliance/resources/schemas/dbscripts/postgresql/wnprc_compliance-25.000-25.001.sql new file mode 100644 index 000000000..3d91d1074 --- /dev/null +++ b/WNPRC_Compliance/resources/schemas/dbscripts/postgresql/wnprc_compliance-25.000-25.001.sql @@ -0,0 +1,23 @@ +ALTER TABLE wnprc_compliance.card_info ADD COLUMN IF NOT EXISTS card_type text; +ALTER TABLE wnprc_compliance.card_info ADD COLUMN IF NOT EXISTS date_issued timestamp; +ALTER TABLE wnprc_compliance.card_info ADD COLUMN IF NOT EXISTS date_expire timestamp; +ALTER TABLE wnprc_compliance.card_info ADD COLUMN IF NOT EXISTS issue_code int; + +ALTER TABLE wnprc_compliance.access_report_data RENAME TO access_report_data_old; + + +DROP TABLE IF EXISTS wnprc_compliance.access_report_data; +CREATE TABLE wnprc_compliance.access_report_data ( + report_id TEXT, + access_level TEXT, + card_id TEXT, + + container entityid NOT NULL, + createdby userid, + created TIMESTAMP, + modifiedby userid, + modified TIMESTAMP, + + CONSTRAINT PK_access_report_data_new PRIMARY KEY (report_id, access_level, card_id), + CONSTRAINT FK_access_report_data_access_reports_new FOREIGN KEY (report_id) REFERENCES wnprc_compliance.access_reports (report_id) +); diff --git a/WNPRC_Compliance/resources/schemas/wnprc_compliance.xml b/WNPRC_Compliance/resources/schemas/wnprc_compliance.xml index 80be1b01e..250b6ace1 100644 --- a/WNPRC_Compliance/resources/schemas/wnprc_compliance.xml +++ b/WNPRC_Compliance/resources/schemas/wnprc_compliance.xml @@ -135,6 +135,18 @@ + + + + + + + + + + +
+ @@ -156,6 +168,10 @@ + + + + diff --git a/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportRowParser.java b/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportRowParser.java index ddf333310..6dff03474 100644 --- a/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportRowParser.java +++ b/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportRowParser.java @@ -8,6 +8,7 @@ import org.labkey.api.data.Container; import org.labkey.api.util.Pair; +import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -16,6 +17,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -23,20 +25,14 @@ */ public class AccessReportRowParser { enum ColumnName { - FIRST_NAME (false, "FirstName"), - LAST_NAME (false, "LastName"), - MIDDLE_NAME (false, "MiddleName"), - DEPARTMENT (false, "department"), - EMPLOYEE_NUMBER (false, "empNumber"), - CARD_NUMBER (true, "CardNumber"), - STATE (false, "state"), - TIME_ENTERED (false, "timeEntered", Date.class), - INFO2 (false, "info2"), - INFO3 (false, "info3"), - INFO5 (false, "info5"), - // This column only appears some times, but it appears to be the access schedule for the employee (what - // hours they're allowed to use the door). - SCHEDULE_PT (false, "scheuld_pt") + FIRST_NAME (false, "Name (Last, First, Middle)"), + LAST_NAME (false, "Name (Last, First, Middle)"), + MIDDLE_NAME (false, "Name (Last, First, Middle)"), + CARD_NUMBER (true, "Badge ID(Issue)"), + CARD_ISSUED (false, "Badge Active"), + CARD_EXPIRE (false, "Badge Deactive"), + BADGE_TYPE (false, "Badge Type"), + CARD_ISSUE_CODE (false, "Badge Id(Issue)"), ; boolean required; @@ -91,46 +87,75 @@ public AccessReportRowParser(Row headerRow) throws MalformedReportException { } } - public Pair parseRow(String reportId, Row row, Container container) { + public Pair parseRow(String reportId, Row row, Container container) throws ParseException + { Map values = new HashMap<>(); + + //TODO maybe just grab the names directly instead of this loop for (ColumnName columnName : cellIndexLookup.keySet()) { Cell cell = row.getCell(cellIndexLookup.get(columnName)); - - if (cell != null) { - if (columnName.type == Date.class) { - Date value; - if (cell.getCellType() == CellType.STRING) { - value = parseDate(cell.getStringCellValue()); + if (cell != null ) { + if (columnName.headerText.equals(ColumnName.FIRST_NAME.headerText)) + { + if (cell.getCellType() == CellType.STRING && !cell.getStringCellValue().isEmpty()) + { + Matcher matcher = Pattern.compile("^(.*?),\\s+(\\w+)(?:\\s+(\\w+))?$").matcher(cell.getStringCellValue()); + + if (matcher.find()) + { + values.put(ColumnName.FIRST_NAME, matcher.group(2)); + values.put(ColumnName.LAST_NAME, matcher.group(1)); + values.put(ColumnName.MIDDLE_NAME, matcher.group(3)); + } } - else if (cell.getCellType() == CellType.NUMERIC) { - value = cell.getDateCellValue(); + + } + + if (columnName.headerText.equals(ColumnName.CARD_ISSUED.headerText)) + { + if (!cell.toString().isEmpty()) + { + DateFormat df = new SimpleDateFormat("dd-MMM-yyyy"); + Date d = df.parse(cell.toString()); + values.put(ColumnName.CARD_ISSUED, d); + } - else if (cell.getCellType() == CellType.BLANK) { - value = null; + } + if (columnName.headerText.equals(ColumnName.CARD_EXPIRE.headerText)) + { + if (!cell.toString().isEmpty()) + { + DateFormat df = new SimpleDateFormat("dd-MMM-yyyy"); + Date d = df.parse(cell.toString()); + values.put(ColumnName.CARD_EXPIRE, d); } - else { - throw new ApiUsageException("Unrecognized type in date column"); + } + + if (columnName.headerText.equals(ColumnName.CARD_NUMBER.headerText)) + { + + if (cell.getCellType() == CellType.STRING && !cell.getStringCellValue().isEmpty()) + { + Matcher matcher = Pattern.compile("(\\d+)\\s*\\((\\d+)\\)").matcher(cell.getStringCellValue()); + if (matcher.find()) + { + values.put(ColumnName.CARD_NUMBER, matcher.group(1)); + values.put(ColumnName.CARD_ISSUE_CODE, matcher.group(2)); + } } + } - values.put(columnName, value); + String value = ""; + if (cell.getCellType() == CellType.STRING) { + value = cell.getStringCellValue(); } - else { - //For whatever reason apache ROI library thinks some of these cells are numeric, - //Even though excel says they are text - String value; - if (cell.getCellType() == CellType.NUMERIC) { - value = NumberToTextConverter.toText(cell.getNumericCellValue()); - } - else { - value = cell.getStringCellValue(); - } + if (!values.containsKey(columnName)) values.put(columnName, value); - } } } - Pair pair = new Pair<>(new CardInfo(values), new AccessInfo(values)); + Pair pair = new Pair<>(new CardInfo(values), new AccessInfo(values)); return pair; } @@ -153,29 +178,26 @@ public String getLastName() { return (String) this.values.get(ColumnName.LAST_NAME); } - public String getDepartment() { - return (String) this.values.get(ColumnName.DEPARTMENT); - } - public String getCardNumber() { return (String) this.values.get(ColumnName.CARD_NUMBER); } - - public String getEmployeeNumber() { - return (String) this.values.get(ColumnName.EMPLOYEE_NUMBER); + public Date getCardIssued() { + return (Date) this.values.get(ColumnName.CARD_ISSUED); } - - public String getInfo2() { - return (String) this.values.get(ColumnName.INFO2); + public Date getCardExpire() { + return (Date) this.values.get(ColumnName.CARD_EXPIRE); } - - public String getInfo3() { - return (String) this.values.get(ColumnName.INFO3); + public String getIssueCode() { + return (String) this.values.get(ColumnName.CARD_ISSUE_CODE); + } + public String getCardType() { + return (String) this.values.get(ColumnName.BADGE_TYPE); } - public String getInfo5() { - return (String) this.values.get(ColumnName.INFO5); + public Map getValues() { + return this.values; } + } public static class AccessInfo { @@ -185,26 +207,6 @@ public AccessInfo(Map values) { this.values = values; } - public String getSchedule() { - return (String) this.values.get(ColumnName.SCHEDULE_PT); - } - - public Date getLastEntered() { - return (Date) this.values.get(ColumnName.TIME_ENTERED); - } - - public boolean isEnabled() { - String state = (String) this.values.get(ColumnName.STATE); - if (state.equalsIgnoreCase("Enabled") || state.equalsIgnoreCase("")) { - return true; - } - else if (state.equalsIgnoreCase("DISABLED")) { - return false; - } - else { - throw new ApiUsageException("Unrecognized value in 'state' column: " + state); - } - } } public static class MalformedReportException extends Exception { @@ -214,7 +216,7 @@ public MalformedReportException(String message) { } public static Date parseDate(String dateString) { - SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a"); + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy hh:mm:ssa"); SimpleDateFormat shortDateFormat = new SimpleDateFormat("MM/dd/yyyy"); Date date; diff --git a/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportService.java b/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportService.java index 23220f2f6..576ba1a7c 100644 --- a/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportService.java +++ b/WNPRC_Compliance/src/org/labkey/wnprc_compliance/AccessReportService.java @@ -3,7 +3,9 @@ import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; import org.json.JSONObject; +import org.junit.Assert; import org.labkey.api.action.ApiUsageException; import org.labkey.api.data.CompareType; import org.labkey.api.data.Container; @@ -15,6 +17,7 @@ import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.InvalidKeyException; import org.labkey.api.query.QueryUpdateServiceException; +import org.labkey.api.reader.ExcelLoader; import org.labkey.api.security.User; import org.labkey.api.util.JsonUtil; import org.labkey.api.util.Pair; @@ -26,6 +29,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -50,24 +54,30 @@ public AccessReportService(User user, Container container) { this.container = container; } - public void importReport(InputStream stream) throws IOException, AccessReportRowParser.MalformedReportException { + public void importReport(InputStream stream) throws IOException, AccessReportRowParser.MalformedReportException, ParseException + { String reportid = UUID.randomUUID().toString().toUpperCase(); + Sheet sheet = new ExcelLoader(stream,false, null).getSheet(); - HSSFWorkbook workbook = new HSSFWorkbook(stream); - HSSFSheet sheet = workbook.getSheetAt(0); - - Row titleRow = sheet.getRow(2); - if (!titleRow.getCell(0).getStringCellValue().equalsIgnoreCase("Area Rights Report")) { + Row titleRow = sheet.getRow(1); + if (!titleRow.getCell(0).getStringCellValue().equalsIgnoreCase("Access Level Assignments to Cardholders")) { throw new ApiUsageException("You can only upload area rights reports here."); } Row dateRow = sheet.getRow(6); - Pattern datePattern = Pattern.compile("report created on (.*)"); - Matcher datePatternMatcher = datePattern.matcher(dateRow.getCell(0).getStringCellValue()); - if (!datePatternMatcher.matches()) { - throw new ApiUsageException("Could not parse date of report"); + //the report created date is the 13th column over + Matcher matcher = Pattern.compile("Report\\s+Date:\\s*(\\d{2}/\\d{2}/\\d{4}\\s+\\d{1,2}:\\d{2}:\\d{2}[AP]M)").matcher(dateRow.getCell(14).getStringCellValue()); + String reportDateTime; + Date generatedOn; + if (matcher.find()) + { + reportDateTime = matcher.group(1).trim(); + generatedOn = AccessReportRowParser.parseDate(reportDateTime); + } + else + { + throw new ApiUsageException("Unable to parse date/time string"); } - Date generatedOn = AccessReportRowParser.parseDate(datePatternMatcher.group(1)); // Check to make sure we haven't done this already. SimpleQueryFactory queryFactory = new SimpleQueryFactory(user, container); @@ -77,84 +87,103 @@ public void importReport(InputStream stream) throws IOException, AccessReportRow throw new ApiUsageException("This report has already been uploaded."); } - Set areaNames = new HashSet<>(); Map cardInfos = new HashMap<>(); List> accessData = new ArrayList<>(); - Pattern areaNamePattern = Pattern.compile("[\\s\\x00A0]*area: (.*)"); + + //if the cell contains Prim and Barrier + Pattern accessLevelPattern = Pattern.compile("Access Level:\\s*"); Iterator rows = sheet.rowIterator(); + AccessReportRowParser rowParser = null; + String accessLevel = ""; outer: while (rows.hasNext()) { - Row row = rows.next(); - String firstCellText = row.getCell(0).getStringCellValue(); - firstCellText = firstCellText.replaceAll("\u00A0", ""); // strip out non-breaking spaces - - Matcher areaNameMatcher = areaNamePattern.matcher(firstCellText); - if (areaNameMatcher.matches()) { - areaNames.add(areaNameMatcher.group(1)); + Row currentRow = rows.next(); + //don't parse items until we reach the main block + if (currentRow.getRowNum() < 8) + { continue; } + if (currentRow.getCell(4) == null) + { + continue; + } + String firstCellText = currentRow.getCell(0).getStringCellValue(); - if (areaNames.contains(firstCellText)) { - String areaName = firstCellText; + Matcher accessLevelMatcher = accessLevelPattern.matcher(firstCellText); + //we've encountered an access level block of text + //we can grab the header and skip a row + if (accessLevelMatcher.matches()) + { + accessLevel = currentRow.getCell(4).getStringCellValue(); // We are about to go into a block of values. First, eat the blank line rows.next(); // Now eat the header line Row headerRow = rows.next(); - AccessReportRowParser rowParser = new AccessReportRowParser(headerRow); - - inner: - while (rows.hasNext()) { - Row currentRow = rows.next(); - Pair results = rowParser.parseRow(reportid, currentRow, container); - AccessReportRowParser.CardInfo cardInfo = results.first; - AccessReportRowParser.AccessInfo accessInfo = results.second; - - String cardNumber = cardInfo.getCardNumber(); - - // Ignore cards with "0" as the cardNumber - if (cardNumber.equals("0")) { - continue inner; - } - - // If we've hit a blank line, break out - if (cardNumber.equals("")) { - break inner; - } - - JSONObject cardInfoJSON = new JSONObject(); - cardInfoJSON.put("report_id", reportid); - cardInfoJSON.put("card_id", cardNumber); - - cardInfoJSON.put("first_name", cardInfo.getFirstName()); - cardInfoJSON.put("last_name", cardInfo.getLastName()); - cardInfoJSON.put("middle_name", cardInfo.getMiddleName()); - cardInfoJSON.put("department", cardInfo.getDepartment()); - cardInfoJSON.put("employee_number", cardInfo.getEmployeeNumber()); - - cardInfoJSON.put("info2", cardInfo.getInfo2()); - cardInfoJSON.put("info3", cardInfo.getInfo3()); - cardInfoJSON.put("info4", cardInfo.getInfo3()); - cardInfoJSON.put("container", container.getId()); - - cardInfos.put(cardNumber, cardInfoJSON); - - JSONObject accessInfoJSON = new JSONObject(); - accessInfoJSON.put("report_id", reportid); - accessInfoJSON.put("area", areaName); - accessInfoJSON.put("card_id", cardNumber); - accessInfoJSON.put("container", container.getId()); - - accessInfoJSON.put("schedule_pt", accessInfo.getSchedule()); - accessInfoJSON.put("last_entered", accessInfo.getLastEntered()); - accessInfoJSON.put("enabled", accessInfo.isEnabled()); - - accessData.add(accessInfoJSON.toMap()); + //sets up the column names from the header row? + + // + rowParser = new AccessReportRowParser(headerRow); + + currentRow = rows.next(); + } + if (rowParser == null) + { + continue; + } + Pair results = rowParser.parseRow(reportid, currentRow, container); + AccessReportRowParser.CardInfo cardInfo = results.first; + AccessReportRowParser.AccessInfo accessInfo = results.second; + + if (cardInfo.getValues().isEmpty()) + continue; + + + //end of spreadsheet pattern + Pattern endOfSheetPattern = Pattern.compile("Total Badges Required for Download:"); + if (endOfSheetPattern.matcher(cardInfo.getFirstName()).matches()) + { + int getNumCards = (int) currentRow.getCell(10).getNumericCellValue(); + if (cardInfos.size() != getNumCards) + { + throw new RuntimeException("Card number does not equal total badge count in sheet, upload failed."); } + break; + } + + + String cardNumber = cardInfo.getCardNumber(); + if (cardNumber == null || cardNumber.equals("")) + { + continue; } + + + JSONObject cardInfoJSON = new JSONObject(); + cardInfoJSON.put("report_id", reportid); + cardInfoJSON.put("card_id", cardNumber); + + cardInfoJSON.put("first_name", cardInfo.getFirstName()); + cardInfoJSON.put("last_name", cardInfo.getLastName()); + cardInfoJSON.put("middle_name", cardInfo.getMiddleName()); + cardInfoJSON.put("date_issued", cardInfo.getCardIssued()); + cardInfoJSON.put("date_expire", cardInfo.getCardExpire()); + cardInfoJSON.put("issue_code", cardInfo.getIssueCode()); + cardInfoJSON.put("card_type", cardInfo.getCardType()); + cardInfoJSON.put("container", container.getId()); + + cardInfos.put(cardNumber, cardInfoJSON); + + JSONObject accessInfoJSON = new JSONObject(); + accessInfoJSON.put("report_id", reportid); + accessInfoJSON.put("access_level", accessLevel); + accessInfoJSON.put("card_id", cardNumber); + accessInfoJSON.put("container", container.getId()); + + accessData.add(accessInfoJSON.toMap()); } try (DbScope.Transaction transaction = DbSchema.get(WNPRC_ComplianceSchema.NAME, DbSchemaType.Module).getScope().ensureTransaction()) {