From f7d1bbf16ae7d591ac4b7d14725c1798f087cae7 Mon Sep 17 00:00:00 2001 From: Kenneth Lee Date: Tue, 10 Dec 2024 17:03:08 +0000 Subject: [PATCH] CAPT-2040 normalize names for payroll csv export --- app/models/payroll/name_normalizer.rb | 39 ++++++++++++++ app/models/payroll/payment_csv_row.rb | 12 +++++ spec/models/payroll/name_normalizer_spec.rb | 57 +++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 app/models/payroll/name_normalizer.rb create mode 100644 spec/models/payroll/name_normalizer_spec.rb diff --git a/app/models/payroll/name_normalizer.rb b/app/models/payroll/name_normalizer.rb new file mode 100644 index 0000000000..1498179dad --- /dev/null +++ b/app/models/payroll/name_normalizer.rb @@ -0,0 +1,39 @@ +module Payroll + class NameNormalizer + def self.normalize(name) + new(name).normalize + end + + def initialize(name) + @name = name + end + + def normalize + return nil if @name.nil? + + @name + .then { |n| remove_typical_disallowed_chars(n) } + .then { |n| transliterate(n) } + .then { |n| final_cleanup(n) } + end + + private + + # Things we allow from `NameFormatValidator` and typically found in names + # but Payroll provider doesn't accept + # Also handles curly apostrophe commonly found + def remove_typical_disallowed_chars(name) + name.gsub(/[,.;\-'‘’\s]/, "") + end + + # Attempt to replace things like `è` with `e` + def transliterate(name) + I18n.transliterate(name) + end + + # Just remove anything missed that aren't allowed by Payroll + def final_cleanup(name) + name.gsub(/[^A-Za-z]/, "") + end + end +end diff --git a/app/models/payroll/payment_csv_row.rb b/app/models/payroll/payment_csv_row.rb index cf34456281..52b17c29f6 100644 --- a/app/models/payroll/payment_csv_row.rb +++ b/app/models/payroll/payment_csv_row.rb @@ -27,6 +27,18 @@ def to_a private + def first_name + NameNormalizer.normalize(model.first_name) + end + + def middle_name + NameNormalizer.normalize(model.middle_name) + end + + def surname + NameNormalizer.normalize(model.surname) + end + def title TITLE end diff --git a/spec/models/payroll/name_normalizer_spec.rb b/spec/models/payroll/name_normalizer_spec.rb new file mode 100644 index 0000000000..5d87f8c87d --- /dev/null +++ b/spec/models/payroll/name_normalizer_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +RSpec.describe Payroll::NameNormalizer do + describe ".normalize" do + subject { described_class.normalize(name) } + + context "nil" do + let(:name) { nil } + it { is_expected.to be_nil } + end + + context "empty string" do + let(:name) { "" } + it { is_expected.to eq "" } + end + + context "blank string" do + let(:name) { " " } + it { is_expected.to eq "" } + end + + context "name with nothing to change" do + let(:name) { "John" } + it { is_expected.to eq "John" } + end + + context "name curly quotes" do + let(:name) { "O’Something" } + it { is_expected.to eq "OSomething" } + end + + context "name accents and spaces" do + let(:name) { "Óscar Hernández" } + it { is_expected.to eq "OscarHernandez" } + end + + context "name with multiple spaces" do + let(:name) { "Chan Chiu Bruce" } + it { is_expected.to eq "ChanChiuBruce" } + end + + context "name with emojis spaces" do + let(:name) { "Thumbs 👍 Up " } + it { is_expected.to eq "ThumbsUp" } + end + + context "name with semi-colon" do + let(:name) { "Samuel;" } + it { is_expected.to eq "Samuel" } + end + + context "name that has it all" do + let(:name) { "Jámes', Ryan, O’Hughes 👍" } + it { is_expected.to eq "JamesRyanOHughes" } + end + end +end