-
-
Notifications
You must be signed in to change notification settings - Fork 222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Case Import Payment Validation #35803
base: master
Are you sure you want to change the base?
Changes from 6 commits
16c0438
4b7401b
80fb505
24971cd
926eb08
9da1701
d1d68f2
523002f
36a93de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,13 +29,19 @@ | |
BULK_UPLOAD_DATE_OPENED, | ||
CASE_IMPORT_DATA_DICTIONARY_VALIDATION, | ||
DOMAIN_PERMISSIONS_MIRROR, | ||
MOBILE_WORKER_VERIFICATION, | ||
) | ||
from corehq.util.metrics import metrics_counter, metrics_histogram | ||
from corehq.util.metrics.load_counters import case_load_counter | ||
from corehq.util.soft_assert import soft_assert | ||
from corehq.util.timer import TimingContext | ||
|
||
from .const import LookupErrors | ||
from .const import ( | ||
LookupErrors, | ||
MOMO_REQUIRED_PAYMENT_FIELDS, | ||
MOMO_NO_EDIT_PAYMENT_FIELDS, | ||
MOMO_PAYMENT_CASE_TYPE, | ||
) | ||
from .exceptions import ( | ||
BlankExternalId, | ||
CaseGeneration, | ||
|
@@ -169,6 +175,12 @@ def _do_import(self, spreadsheet): | |
def import_row(self, row_num, raw_row, import_context): | ||
search_id = self._parse_search_id(raw_row) | ||
fields_to_update = self._populate_updated_fields(raw_row) | ||
if ( | ||
self.mobile_worker_verification_ff_enabled | ||
and self.submission_handler.case_type == MOMO_PAYMENT_CASE_TYPE | ||
): | ||
self._validate_payment_fields(fields_to_update) | ||
|
||
if self._has_custom_case_import_operations(): | ||
fields_to_update = self._perform_custom_case_import_operations( | ||
row_num, | ||
|
@@ -207,6 +219,29 @@ def import_row(self, row_num, raw_row, import_context): | |
|
||
self.submission_handler.add_caseblock(RowAndCase(row_num, caseblock)) | ||
|
||
def _validate_payment_fields(self, fields): | ||
errors = [] | ||
for field_name in MOMO_NO_EDIT_PAYMENT_FIELDS: | ||
if fields.get(field_name): | ||
errors.append(CaseRowError( | ||
column_name=field_name, | ||
message=_( | ||
'This field value cannot be set for cases with the ' | ||
'"{}" case type.' | ||
).format(MOMO_PAYMENT_CASE_TYPE), | ||
)) | ||
for field_name in MOMO_REQUIRED_PAYMENT_FIELDS: | ||
if field_name not in fields or not fields[field_name]: | ||
errors.append(CaseRowError( | ||
column_name=field_name, | ||
message=_( | ||
'This field requires a value for cases with the ' | ||
'"{}" case type.' | ||
).format(MOMO_PAYMENT_CASE_TYPE), | ||
)) | ||
if any(errors): | ||
raise CaseRowErrorList(errors) | ||
|
||
def _has_custom_case_import_operations(self): | ||
return any( | ||
extension.should_call_for_domain(self.domain) | ||
|
@@ -227,6 +262,10 @@ def _perform_custom_case_import_operations(self, row_num, raw_row, fields_to_upd | |
def user(self): | ||
return CouchUser.get_by_user_id(self.config.couch_user_id) | ||
|
||
@cached_property | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
def mobile_worker_verification_ff_enabled(self): | ||
return MOBILE_WORKER_VERIFICATION.enabled(self.domain) | ||
|
||
def _parse_search_id(self, row): | ||
""" Find and convert the search id in an Excel row """ | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ | |
ALL_CASE_TYPE_IMPORT, | ||
MAX_CASE_IMPORTER_COLUMNS, | ||
MAX_CASE_IMPORTER_ROWS, | ||
MOMO_REQUIRED_PAYMENT_FIELDS, | ||
MOMO_PAYMENT_CASE_TYPE, | ||
) | ||
from corehq.apps.case_importer.exceptions import ( | ||
CustomImporterError, | ||
|
@@ -53,7 +55,7 @@ | |
) | ||
from corehq.apps.users.decorators import require_permission | ||
from corehq.apps.users.models import HqPermissions | ||
from corehq.toggles import DOMAIN_PERMISSIONS_MIRROR | ||
from corehq.toggles import DOMAIN_PERMISSIONS_MIRROR, MOBILE_WORKER_VERIFICATION | ||
from corehq.util.view_utils import absolute_reverse | ||
from corehq.util.workbook_reading import ( | ||
SpreadsheetFileExtError, | ||
|
@@ -291,14 +293,24 @@ def _process_excel_mapping(domain, spreadsheet, search_column): | |
def _create_bulk_configs(domain, request, case_upload): | ||
all_configs = [] | ||
worksheet_titles = _get_workbook_sheet_names(case_upload) | ||
mobile_worker_verification_ff_enabled = MOBILE_WORKER_VERIFICATION.enabled(domain) | ||
mkangia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
errors = [] | ||
for index, title in enumerate(worksheet_titles): | ||
with case_upload.get_spreadsheet(index) as spreadsheet: | ||
_, excel_fields, _ = _process_excel_mapping( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. double underscores! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah these were changed to double underscores otherwise they overshadow the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you think the double underscores don't look right, I can always change these to have variable names instead 🤔 |
||
__, excel_fields, __ = _process_excel_mapping( | ||
domain, | ||
spreadsheet, | ||
request.POST['search_field'] | ||
) | ||
excel_fields = list(set(excel_fields) - set(RESERVED_FIELDS)) | ||
if mobile_worker_verification_ff_enabled and title == MOMO_PAYMENT_CASE_TYPE: | ||
missing_fields = _check_payment_fields_exist(excel_fields) | ||
if missing_fields: | ||
errors.append(_( | ||
'Sheet with case type "{}" is missing one or more required ' | ||
'fields: {}' | ||
).format(title, ', '.join(missing_fields))) | ||
break | ||
config = importer_util.ImporterConfig.from_dict({ | ||
'couch_user_id': request.couch_user._id, | ||
'excel_fields': excel_fields, | ||
|
@@ -310,7 +322,7 @@ def _create_bulk_configs(domain, request, case_upload): | |
'create_new_cases': request.POST['create_new_cases'] == 'True', | ||
}) | ||
all_configs.append(config) | ||
return all_configs | ||
return all_configs, errors | ||
|
||
|
||
@require_POST | ||
|
@@ -377,6 +389,16 @@ def excel_fields(request, domain): | |
|
||
case_field_specs = [field_spec.to_json() for field_spec in field_specs] | ||
|
||
if MOBILE_WORKER_VERIFICATION.enabled(domain) and case_type == MOMO_PAYMENT_CASE_TYPE: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to worry about bulk import? Seeing at the function documentation
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should accommodate bulk imports too, as noted by this Jira comment. It is true that a bulk import will have a special case type and so this check will fail, however we handle validation for bulk imports separately within the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
and so the columns wont be verified for a bulk import here but they are verified in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is correct. The config for a bulk import only gets created in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does that catch missing columns as well or just checks for values that should not be set? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check in |
||
missing_fields = _check_payment_fields_exist(columns) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: since the function is return missing fields, I'd suggest renaming the function to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed in 523002f. |
||
if any(missing_fields): | ||
return render_error( | ||
request, | ||
domain, | ||
_('The Excel file is missing the following required fields for the "{}" case type: {}').format( | ||
MOMO_PAYMENT_CASE_TYPE, ', '.join(missing_fields) | ||
) | ||
) | ||
context = { | ||
'case_type': case_type, | ||
'search_column': search_column, | ||
|
@@ -421,7 +443,9 @@ def excel_commit(request, domain): | |
case_type = request.POST['case_type'] | ||
all_configs = [] | ||
if case_type == ALL_CASE_TYPE_IMPORT: | ||
all_configs = _create_bulk_configs(domain, request, case_upload) | ||
all_configs, errors = _create_bulk_configs(domain, request, case_upload) | ||
if any(errors): | ||
return render_error(request, domain, ', '.join(errors)) | ||
else: | ||
config = importer_util.ImporterConfig.from_request(request) | ||
all_configs = [config] | ||
|
@@ -483,7 +507,9 @@ def _bulk_case_upload_api(request, domain): | |
|
||
all_configs = [] | ||
if context['is_bulk_import']: | ||
all_configs = _create_bulk_configs(domain, request, case_upload) | ||
all_configs, errors = _create_bulk_configs(domain, request, case_upload) | ||
if any(errors): | ||
raise ImporterError(', '.join(errors)) | ||
else: | ||
with case_upload.get_spreadsheet() as spreadsheet: | ||
_, excel_fields, _ = _process_excel_mapping(domain, spreadsheet, search_column) | ||
|
@@ -517,3 +543,10 @@ def _bulk_case_upload_api(request, domain): | |
status_url = absolute_reverse('case_importer_upload_status', args=(domain, upload_id)) | ||
|
||
return json_response({"code": 200, "message": "success", "status_url": status_url}) | ||
|
||
|
||
def _check_payment_fields_exist(columns): | ||
return [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be better to use sets here to find the missing fields? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean by doing something as follows?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a performance perspective the difference will be neglible for a list this small, but using sets does make the code footprint a tad less and perhaps a bit easier to read. I'll update this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed in 985e235. |
||
field_name for field_name in MOMO_REQUIRED_PAYMENT_FIELDS | ||
if field_name not in columns | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3010,3 +3010,10 @@ def domain_has_privilege_from_toggle(privilege_slug, domain): | |
tag=TAG_SOLUTIONS, | ||
namespaces=[NAMESPACE_DOMAIN], | ||
) | ||
|
||
MOBILE_WORKER_VERIFICATION = StaticToggle( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: if this is only for MTN I'd suggest renaming this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed in d1d68f2. |
||
slug='mobile_worker_verification', | ||
label='Enable user verification using MTN Mobile Money', | ||
tag=TAG_SOLUTIONS, | ||
namespaces=[NAMESPACE_DOMAIN], | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason for this check to be during import and not earlier when uploading file?
is it because this could take really long so we don't want to do it then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main motivation for doing it here is that this is the part in the import where we're looping through all the sheet rows, and so we're able to easily see if there are cell values with empty required fields.
Doing this at an earlier point would mean we would have to do an extra loop through all the rows to validate their values which felt unnecessary. Additionally, failing it here simply results in the row not getting imported. This therefore then doesn't block the entire import and other rows can still get imported if they're valid.