Skip to content
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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

zandre-eng
Copy link
Contributor

Product Description

If an Excel file import for the payment case type does not include headers for all the required fields, then the following error will be shown after continuing past step 2 of the import process:
Screenshot from 2025-02-19 14-51-01

Additionally, if the import contains rows that either does not have values for the required fields, or sets values for the no edit fields, then the following errors will be shown in the import summary:
Screenshot from 2025-02-19 14-49-32

Technical Summary

Link to ticket here.
Link to tech spec here.

This PR adds some extra validation to the case import workflow when a user is importing cases for the payment case type. This extra validation seeks to ensure that all the necessary information exists on the case for payment verification.

Feature Flag

MOBILE_USER_VERIFICATION

Safety Assurance

Safety story

  • Local testing done

Automated test coverage

Automated tests do exist.

QA Plan

No QA planned.

Rollback instructions

  • This PR can be reverted after deploy with no further considerations

Labels & Review

  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@zandre-eng zandre-eng added the product/feature-flag Change will only affect users who have a specific feature flag enabled label Feb 19, 2025
@dimagimon dimagimon added the Risk: High Change affects files that have been flagged as high risk. label Feb 19, 2025
@zandre-eng zandre-eng marked this pull request as ready for review February 19, 2025 12:58
return render_error(
request,
domain,
_('The Excel file is missing one or more required fields for the "{}" case type: {}'.format(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nit: The error message as is was a little surprising to me when it listed out the missing case types since the message didn't sound like it will do that.

How about changing the text to something below (in my mind it reads a little better)?

The Excel file is missing the following required fields for the "{}" case type: {}

(it is very nit, so feel free to ignore if you want, but I thought I'd mention in nonetheless)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion does make the text feel a little more streamlined and easier to read. Happy to update this.

Addressed in 926eb08.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think the correct way to use the translation would be
_('The Excel file ... "{}" case type: {}').format()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mkangia You're absolutely right. Here is a code ref that confirms how the translation should be. I'll update this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed translation formatting in 9da1701.

@@ -3010,3 +3010,10 @@ def domain_has_privilege_from_toggle(privilege_slug, domain):
tag=TAG_SOLUTIONS,
namespaces=[NAMESPACE_DOMAIN],
)

MOBILE_WORKER_VERIFICATION = StaticToggle(
Copy link
Contributor

Choose a reason for hiding this comment

The 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 MTN_MOBILE_WORKER_VERIFICATION OR MTN_USER_VERIFICATION.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed in d1d68f2.

@@ -377,6 +379,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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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

case_type:
        For a bulk import, this will be displayed
        as the special value for bulk case import/export, and the fetching of
        the case types will be handled in the next step

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 _create_bulk_configs() function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this check will fail

and so the columns wont be verified for a bulk import here but they are verified in _create_bulk_configs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 excel_commit() and that is where _create_bulk_configs() is called which will then validate for a bulk import.

Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check in _create_bulk_configs() will only validate that all the required headers are included. When checking for the values themselves, this will happen in the same place as a single case type import within do_import.py::_validate_payment_fields()

@@ -377,6 +379,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:
missing_fields = _check_payment_fields_exist(columns)
Copy link
Contributor

Choose a reason for hiding this comment

The 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 _get_missing_payment_fields

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed in 523002f.

return render_error(
request,
domain,
_('The Excel file is missing one or more required fields for the "{}" case type: {}'.format(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think the correct way to use the translation would be
_('The Excel file ... "{}" case type: {}').format()



def _check_payment_fields_exist(columns):
return [
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean by doing something as follows?

return set(MOMO_REQUIRED_PAYMENT_FIELDS) - set(columns)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 985e235.

@@ -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 (
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@@ -297,7 +297,7 @@ def _create_bulk_configs(domain, request, case_upload):
errors = []
for index, title in enumerate(worksheet_titles):
with case_upload.get_spreadsheet(index) as spreadsheet:
_, excel_fields, _ = _process_excel_mapping(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double underscores!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah these were changed to double underscores otherwise they overshadow the gettext import which is imported as _.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 🤔

@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@mkangia mkangia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good.

Just wanted to check once about testing on this. We have tested that this does not impact imports outside the feature flag i.e the blast radius is limited to only the feature flagged domains?

@zandre-eng
Copy link
Contributor Author

Just wanted to check once about testing on this. We have tested that this does not impact imports outside the feature flag i.e the blast radius is limited to only the feature flagged domains?

@mkangia With local testing everything looks fine. I tested with the following workflows:

  • Standard single case type import.
  • Single case type payment import.
    • This includes checking for errors with headers and missing values.
  • Bulk import including payment case type.
  • Standard bulk import.

All the code added in this PR sits behind the FF conditional, and so if it were to break anything I expect this would only be when (a) the FF is enabled and (b) a user is attempting a payment import.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
product/feature-flag Change will only affect users who have a specific feature flag enabled Risk: High Change affects files that have been flagged as high risk.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants