class Eco::API::UseCases::DefaultCases::TransferAccountCase
Public Instance Methods
Usecase to **actually transfer a user/account** from one person to another person in the organization.
* **invocation command**: `-transfer-account-from`
These are the steps and jobs it does:
1. **pair** person entries (note: the `destination-id` entry could not be present, it will add it in such a case). 2. **retrieve** from server persons that were not included in `people`. 3. **validation** - a person should only receive account from just one user - a person should only give account to just one user - every account giver should have account 4. **create jobs** - **move** giver's and receiver's **accounts** to dummy email addresses. * dummy email pattern: from name@domain.ltd --to--> demo+name.domain.ltd@ecoportal.co.nz - **free up** giver's and receiver's **accounts**. - **switch** email: set receiver's email to that of giver's dummy address. * to ensure account transfer, as we moved accounts to dummy emails, those dummy addresses should be used - **invite** receivers: adds account to the destination person in the dummy email. * actual user/account transfer to the person/receiver * **no notification** will be recived by the user, because of the dummy address at this stage - **restore** email: sets the receiver email from the dummy address to the final email. * the final email inbox will receive a **notification** of _email change_
@note
- the `csv` should contain a column `destination-id` containing the person `id` or `external_id` of the person that will be receiving the account. - when running this case, it is recommended to use the option `-skip-batch-policies` - it is highly recommended to either refresh the cache with `-get-people` or use the `-get-partial` option.
@param entries [Eco::API::Common::People::Entries] the input entries with the data. @param people [Eco::API::Organization::People] target existing People of the current update. @param session [Eco::API::Session] the current session where the usecase kicks in. @param options [Hash] the options that modify the case behaviour or bring some dependencies. @option options [Hash<Symbol, Object>] :include things that should be included.
* `:email` (Boolean) [false] if the `email` should be transferred as well (**command option**: `-include-email`)
@option options [Hash<Symbol, Object>] :skip things that should be excluded.
* `:api_policies` (Boolean) [false] if the `api policies` should be skipped (**command option**: `-skip-api-policies`)
@return [Void]
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 39 def main(entries, people, session, options, usecase) micro = session.micro move = session.new_job("main", "move email accounts", :update, usecase, :account) free = session.new_job("main", "free up accounts", :update, usecase, :account) switch = session.new_job("main", "switch email", :update, usecase, :core) invite = session.new_job("post", "invite receivers", :update, usecase, :account) restore = session.new_job("post", "restore email", :update, usecase, :core) with_each_person_pair(entries, people, session, options) do |src_person, dst_person| # capture actual initial information src_doc = src_person.account.doc src_email = src_person.email src_dummy = dummy_email(src_person) dst_email = dst_person.email dst_dummy = dummy_email(dst_person) copy_src_email = options.dig(:include, :email) || !dst_person.account dst_end_email = copy_src_email ? src_email : dst_email # account email renamings are necessary to avoid uncertainty and ensure no email taken error move.add(dst_person) {|dst| dst.email = dst_dummy} move.add(src_person) {|src| src.email = src_dummy} # free accounts up! free.add([dst_person, src_person]) {|person| person.account = nil} # to effectivelly transfer the user/account, email should be the same during invite # otherwise the account doesn't actually get transferred but just copied switch.add(dst_person) {|dst| dst.email = src_dummy} # do the actual transfer of account invite.add(dst_person) {|dst| dst.account = src_doc} # recover the original email, if the receiver had account restore.add(dst_person) {|dst| dst.email = dst_end_email} end.tap do |units| if options[:simulate] units.each {|unit| puts unit.persons.map(&:external_id).join(" --> ")} end end end
Private Instance Methods
givers go first
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 180 def <=>(other) return -1 if src_person == other.dst_person return 1 if dst_person == other.src_person 0 end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 152 def decouple_peer(entry) entry.new({ "id" => entry_peer_id(entry), "external_id" => entry_peer_id(entry), "email" => entry_peer_id(entry), "idx" => entry.idx }) end
if the person has account and an email different to demo+__@ecoportal.co.nz it transforms email to demo+__@ecoportal.co.nz
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 80 def dummy_email(person) return nil unless email = person.email return email if email.start_with?("demo") && email.end_with?("@ecoportal.co.nz") return email unless person.account "demo+#{email.split("@").join(".")}@ecoportal.co.nz" end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 147 def entry_peer(entry, entries) return nil unless peer_id = entry_peer_id(entry) entries.entry(id: peer_id, external_id: peer_id) || decouple_peer(entry) end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 161 def entry_peer_id(entry) dest_id = entry.final_entry["destination-id"] return dest_id unless dest_id.to_s.strip.empty? end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 166 def entry_peer_id?(entry) entry.final_entry.key?("destination-id") end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 251 def expect_destination_id!(entries, logger) unless entries.length > 0 logger.error("Your csv is empty") exit(1) end unless entry_peer_id?(entries.first) logger.error("You haven't defined a column 'destination-id' to whom the account should be transferred") exit(1) end end
Units helpers
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 193 def find_repeated(units, &block) units.group_by(&block).select do |k,v| v.count > 1 end.map {|k, v| v.first} end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 188 def new_unit(e1, e2, p1, p2) unit_type.new(e1, e2, p1, p2) end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 133 def paired_entries(entries, session) expect_destination_id!(entries, session.logger) missing_peer = [] entries.each_with_object([]) do |source, out| if peer = entry_peer(source, entries) out.push([source, peer]) else missing_peer.push(source) end end.tap do |paired_entries| yield(missing_peer) if block_given? end end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 173 def persons [src_person, dst_person] end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 262 def report_missing_account(str, logger) logger.error("The following source people do not have account:\n#{str}") end
Messaging & Validation
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 233 def report_missing_peer!(unpaired_entries, logger) unless unpaired_entries.empty? msg = "The following rows are missing 'destination-id':\n\n" msg += unpaired_entries.map {|entry| str_missing_peer(entry)}.join("\n") logger.error(msg) exit(1) end end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 266 def report_repeated(str, logger, msg = "The following entries are repeated") logger.error("#{msg}\n#{str}") end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 176 def reversed_unit?(u2) (src_person == u2.dst_person) && (dst_person == u2.src_person) end
account givers that are receivers should give first exchangers are placed at the beginning
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 105 def sort_units(units) base = units.dup # cases where two persons exchange account exchangers = base.select do |source| base.any? {|dest| source.reversed_unit?(dest)} end exchangers | base.sort end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 242 def str_missing_peer(entry) "Source #{entry.to_s(:identify)} is missing 'destination-id'." end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 246 def str_person_entry(person, entry, row = nil) str_row = row ? "(actual row: #{row}) " : nil "#{str_row}id: '#{person.id || person.external_id}' email:#{person.email} -> Entry #{entry.to_s(:identify)}" end
Entry helpers
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 115 def to_persons(paired_entry, people, session, options) micro = session.micro [].tap do |persons| micro.with_each(paired_entry, people, options) do |entry, person| next persons.push(person) unless person.new? if person = session.batch.search([entry], silent: true).people.first persons.push(person) people << person next end session.logger.error("This person does not exist: #{entry.to_s(:identify)}") exit(1) end end.tap do |persons| yield(*persons) if block_given? end end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 199 def uniq_array(ary, &block) ary.each_with_object([]) do |e, uniq| uniq.push(e) unless uniq.any? {|chosen| block.call(chosen, e)} end end
Unit type helpers
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 171 def unit_type @unit_type ||= Struct.new(:src_entry, :dst_entry, :src_person, :dst_person) do def persons [src_person, dst_person] end def reversed_unit?(u2) (src_person == u2.dst_person) && (dst_person == u2.src_person) end # givers go first def <=>(other) return -1 if src_person == other.dst_person return 1 if dst_person == other.src_person 0 end end end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 205 def validate_pairs!(units, logger) missing_account = [] src_repeated = find_repeated(units) do |unit| unit.src_person.tap do |src_person| missing_account << (src_person.account ? nil : str_person_entry(unit.src_person, unit.src_entry)) end end.each_with_object([]) do |unit, lines| lines << str_person_entry(unit.src_person, unit.src_entry) end dst_repeated = find_repeated(units) do |unit| unit.dst_person end.each_with_object([]) do |unit, lines| lines << str_person_entry(unit.dst_person, unit.dst_entry, unit.src_entry.idx) end missing_account.compact! unless [missing_account, src_repeated, dst_repeated].all?(&:empty?) report_missing_account(missing_account.join("\n"), logger) unless missing_account.empty? msg = Proc.new do |spot| "Transfers should be a 1.to.1 relation. The following #{spot} entries are repeated in the csv:" end report_repeated(src_repeated.join("\n"), logger, msg["SOURCE"]) unless src_repeated.empty? report_repeated(dst_repeated.join("\n"), logger, msg["DESTINATION"]) unless dst_repeated.empty? exit(1) end end
# File lib/eco/api/usecases/default_cases/transfer_account_case.rb, line 87 def with_each_person_pair(entries, people, session, options) units = paired_entries(entries, session) do |unpaired_entries| report_missing_peer!(unpaired_entries, session.logger) end.map do |entry_pair| person_pair = to_persons(entry_pair, people, session, options) new_unit(*entry_pair, *person_pair) end sort_units(units).tap do |units| # check that there are no repeated sources or destinations, that sources have account validate_pairs!(units, session.logger) units.each do |unit| yield(*unit.persons) if block_given? end end end