eco-helpers 2.0.21 → 2.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +101 -4
- data/eco-helpers.gemspec +0 -1
- data/lib/eco/api/common.rb +0 -1
- data/lib/eco/api/common/loaders.rb +2 -0
- data/lib/eco/api/common/loaders/base.rb +58 -0
- data/lib/eco/api/common/loaders/case_base.rb +33 -0
- data/lib/eco/api/common/loaders/error_handler.rb +2 -2
- data/lib/eco/api/common/loaders/parser.rb +30 -5
- data/lib/eco/api/common/loaders/policy.rb +1 -1
- data/lib/eco/api/common/loaders/use_case.rb +1 -1
- data/lib/eco/api/common/people/default_parsers.rb +1 -0
- data/lib/eco/api/common/people/default_parsers/csv_parser.rb +93 -1
- data/lib/eco/api/common/people/default_parsers/xls_parser.rb +53 -0
- data/lib/eco/api/common/people/entries.rb +83 -14
- data/lib/eco/api/common/people/entry_factory.rb +36 -21
- data/lib/eco/api/common/people/person_attribute_parser.rb +8 -0
- data/lib/eco/api/common/people/person_factory.rb +4 -2
- data/lib/eco/api/common/people/person_parser.rb +8 -2
- data/lib/eco/api/common/people/supervisor_helpers.rb +1 -1
- data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +0 -8
- data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +0 -8
- data/lib/eco/api/microcases/set_core_with_supervisor.rb +4 -2
- data/lib/eco/api/microcases/set_supervisor.rb +29 -8
- data/lib/eco/api/microcases/with_each.rb +7 -3
- data/lib/eco/api/microcases/with_each_starter.rb +3 -2
- data/lib/eco/api/organization/people.rb +7 -1
- data/lib/eco/api/session.rb +18 -7
- data/lib/eco/api/session/batch.rb +1 -1
- data/lib/eco/api/session/batch/job.rb +42 -9
- data/lib/eco/api/usecases.rb +2 -2
- data/lib/eco/api/usecases/base_case.rb +2 -2
- data/lib/eco/api/usecases/base_io.rb +17 -4
- data/lib/eco/api/usecases/default_cases/create_case.rb +10 -1
- data/lib/eco/api/usecases/default_cases/create_details_case.rb +10 -1
- data/lib/eco/api/usecases/default_cases/create_details_with_supervisor_case.rb +10 -1
- data/lib/eco/api/usecases/default_cases/hris_case.rb +25 -1
- data/lib/eco/api/usecases/default_cases/upsert_case.rb +10 -1
- data/lib/eco/cli/config/default/input.rb +63 -10
- data/lib/eco/cli/config/default/options.rb +40 -8
- data/lib/eco/cli/config/default/usecases.rb +16 -0
- data/lib/eco/cli/config/default/workflow.rb +7 -4
- data/lib/eco/cli/config/filters.rb +6 -2
- data/lib/eco/cli/config/filters/input_filters.rb +3 -2
- data/lib/eco/cli/config/filters/people_filters.rb +3 -2
- data/lib/eco/cli/config/help.rb +1 -1
- data/lib/eco/cli/config/options_set.rb +6 -4
- data/lib/eco/cli/config/use_cases.rb +6 -3
- data/lib/eco/cli/scripting/args_helpers.rb +2 -2
- data/lib/eco/csv.rb +2 -0
- data/lib/eco/language/models/collection.rb +5 -2
- data/lib/eco/version.rb +1 -1
- metadata +4 -22
- data/lib/eco/api/common/base_loader.rb +0 -68
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            class Eco::API::Common::People::DefaultParsers::XLSParser < Eco::API::Common::Loaders::Parser
         | 
| 2 | 
            +
              attribute :xls
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              attr_accessor :already_required
         | 
| 5 | 
            +
              attr_reader   :file
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def parser(file, deps)
         | 
| 8 | 
            +
                @file = file
         | 
| 9 | 
            +
                rows.tap {|r| @file = nil}
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def serializer(array_hash, deps)
         | 
| 13 | 
            +
                raise "Not implemented. TODO: using axlsx or rubyXL gems. See: https://spin.atomicobject.com/2017/03/22/parsing-excel-files-ruby/"
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              private
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def headers
         | 
| 19 | 
            +
                raise "You should implement this method"
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def sheet_name
         | 
| 23 | 
            +
                0
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def workbook
         | 
| 27 | 
            +
                require_reading_libs!
         | 
| 28 | 
            +
                Roo::Spreadsheet.open(file)
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              def spreadheet(name_or_index = sheet_name)
         | 
| 32 | 
            +
                workbook.sheet(name_or_index)
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              def rows(target = headers)
         | 
| 36 | 
            +
                begin
         | 
| 37 | 
            +
                  spreadheet.parse(header_search: target)
         | 
| 38 | 
            +
                rescue Roo::HeaderRowNotFoundError => e
         | 
| 39 | 
            +
                  missing  = JSON.parse(e.message)
         | 
| 40 | 
            +
                  logger.warn("The input file is missing these headers: #{missing}")
         | 
| 41 | 
            +
                  present = target - missing
         | 
| 42 | 
            +
                  rows(present)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def require_reading_libs!
         | 
| 47 | 
            +
                return if already_required
         | 
| 48 | 
            +
                require 'roo'
         | 
| 49 | 
            +
                require 'roo-xls'
         | 
| 50 | 
            +
                already_required = true
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            end
         | 
| @@ -5,6 +5,42 @@ module Eco | |
| 5 5 | 
             
                    # Class meant to offer a _collection_ of entries, normally used to get parsed input data.
         | 
| 6 6 | 
             
                    # @attr_reader entries [Array<Eco::API::Common::PeopleEntry] a pure `Array` object.
         | 
| 7 7 | 
             
                    class Entries <  Eco::Language::Models::Collection
         | 
| 8 | 
            +
                      # Error class that allows to handle cases where multiple entries were found for the same criterion.
         | 
| 9 | 
            +
                      # @note its main purpose to prevent the false pairing of duplicates or override information between different people.
         | 
| 10 | 
            +
                      class MultipleSearchResults < StandardError
         | 
| 11 | 
            +
                        attr_reader :candidates, :property
         | 
| 12 | 
            +
                        # @param msg [String] the basic message error.
         | 
| 13 | 
            +
                        # @param candiates [Array<PersonEntry>] the entries that match the same search criterion.
         | 
| 14 | 
            +
                        # @param property [String] the property of the entry model that triggered the error (base of the search criterion).
         | 
| 15 | 
            +
                        def initialize(msg, candidates: [], property: "email")
         | 
| 16 | 
            +
                          @candidates = candidates
         | 
| 17 | 
            +
                          @property   = property
         | 
| 18 | 
            +
                          super(msg + " " + candidates_summary)
         | 
| 19 | 
            +
                        end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        # @param with_index [Boolean] to add an index to each candidate description.
         | 
| 22 | 
            +
                        # @return [Array<String>] the `candidates` identified
         | 
| 23 | 
            +
                        def identify_candidates(with_index: false)
         | 
| 24 | 
            +
                          candidates.map.each_with_index do |entry, i|
         | 
| 25 | 
            +
                            index = with_index     ? "#{i}. " : ""
         | 
| 26 | 
            +
                            "#{index} #{entry.identify}"
         | 
| 27 | 
            +
                          end
         | 
| 28 | 
            +
                        end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                        # @return [Person] the `candidate` in the `index` position
         | 
| 31 | 
            +
                        def candidate(index)
         | 
| 32 | 
            +
                          candidates[index]
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                        private
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                        def candidates_summary
         | 
| 38 | 
            +
                          lines = ["The following entries have the same '#{property}':"]
         | 
| 39 | 
            +
                          lines.concat(identify_candidates(with_index: true)).join("\n  ")
         | 
| 40 | 
            +
                        end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
             | 
| 8 44 | 
             
                      # build the shortcuts of Collection
         | 
| 9 45 | 
             
                      attr_collection :id, :external_id, :email, :name, :supervisor_id
         | 
| 10 46 |  | 
| @@ -54,19 +90,34 @@ module Eco | |
| 54 90 | 
             
                      # @!group Searchers
         | 
| 55 91 |  | 
| 56 92 | 
             
                      # Search function to find an `entry` based on one of different options
         | 
| 93 | 
            +
                      #   It searches an entry using the parameters given.
         | 
| 94 | 
            +
                      # @note This is how the search function actually works:
         | 
| 95 | 
            +
                      #   1. if eP `id` is given, returns the entry (if found), otherwise...
         | 
| 96 | 
            +
                      #   2. if `external_id` is given, returns the entry (if found), otherwise...
         | 
| 97 | 
            +
                      #   3. if `strict` is `false` and `email` is given:
         | 
| 98 | 
            +
                      #     - if there is only 1 entry with that email, returns that entry, otherwise...
         | 
| 99 | 
            +
                      #     - if found but, there are many candidate entries, it raises MultipleSearchResults error
         | 
| 100 | 
            +
                      #     - if entry `external_id` matches `email`, returns that entry
         | 
| 101 | 
            +
                      # @raise MultipleSearchResults if there are multiple entries with the same `email`
         | 
| 102 | 
            +
                      #   and there's no other criteria to find the entry. It only gets to this point if
         | 
| 103 | 
            +
                      #   `external_id` was **not** provided and we are **not** in 'strict' search mode.
         | 
| 104 | 
            +
                      #   However, it could be we were in `strict` mode and `external_id` was not provided.
         | 
| 105 | 
            +
                      # @param id [String] the `internal id` of the person
         | 
| 106 | 
            +
                      # @param external_id [String] the `exernal_id` of the person
         | 
| 107 | 
            +
                      # @param email [String] the `email` of the person
         | 
| 108 | 
            +
                      # @param strict [Boolean] if should perform a `:soft` or a `:strict` search. `strict` will avoid repeated email addresses.
         | 
| 109 | 
            +
                      # @return [Entry, nil] the entry we were searching, or `nil` if not found.
         | 
| 57 110 | 
             
                      def entry(id: nil, external_id: nil, email: nil, strict: false)
         | 
| 58 111 | 
             
                        init_caches
         | 
| 59 | 
            -
                         | 
| 60 | 
            -
                         | 
| 61 | 
            -
                         | 
| 62 | 
            -
             | 
| 63 | 
            -
                         | 
| 64 | 
            -
                         | 
| 65 | 
            -
                         | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
                        end
         | 
| 69 | 
            -
                        pers
         | 
| 112 | 
            +
                        # normalize values
         | 
| 113 | 
            +
                        ext_id   = !external_id.to_s.strip.empty? && external_id.strip
         | 
| 114 | 
            +
                        email    = !email.to_s.strip.empty? && email.downcase.strip
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                        e   = nil
         | 
| 117 | 
            +
                        e ||= @by_id[id]&.first
         | 
| 118 | 
            +
                        e ||= @by_external_id[ext_id]&.first
         | 
| 119 | 
            +
                        e ||= entry_by_email(email) unless strict && ext_id
         | 
| 120 | 
            +
                        e
         | 
| 70 121 | 
             
                      end
         | 
| 71 122 |  | 
| 72 123 | 
             
                      # Search function to find an `entry` based on one of different options
         | 
| @@ -136,15 +187,33 @@ module Eco | |
| 136 187 |  | 
| 137 188 | 
             
                      private
         | 
| 138 189 |  | 
| 190 | 
            +
                      def entry_by_email(email, prevent_multiple_match: false)
         | 
| 191 | 
            +
                        return nil unless email
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                        candidates  = @by_email[email] || []
         | 
| 194 | 
            +
                        return candidates.first if candidates.length == 1
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                        if prevent_multiple_match && !candidates.empty?
         | 
| 197 | 
            +
                          msg = "Multiple search results match the criteria."
         | 
| 198 | 
            +
                          raise MultipleSearchResults.new(msg, candidates: candidates, property: "email")
         | 
| 199 | 
            +
                        end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                        @by_external_id[email]&.first
         | 
| 202 | 
            +
                      end
         | 
| 203 | 
            +
             | 
| 139 204 | 
             
                      def init_caches
         | 
| 140 205 | 
             
                        return if @caches_init
         | 
| 141 | 
            -
                        @by_id          = to_h
         | 
| 142 | 
            -
                        @by_external_id = to_h('external_id')
         | 
| 143 | 
            -
                        @by_email       = to_h('email')
         | 
| 206 | 
            +
                        @by_id          = no_nil_key(to_h)
         | 
| 207 | 
            +
                        @by_external_id = no_nil_key(to_h('external_id'))
         | 
| 208 | 
            +
                        @by_email       = no_nil_key(to_h('email'))
         | 
| 144 209 | 
             
                        @array_supers   = sort_by_supervisors(@items)
         | 
| 145 210 | 
             
                        @caches_init = true
         | 
| 146 211 | 
             
                      end
         | 
| 147 212 |  | 
| 213 | 
            +
                      def no_nil_key(hash)
         | 
| 214 | 
            +
                        hash.tap {|h| h.delete(nil)}
         | 
| 215 | 
            +
                      end
         | 
| 216 | 
            +
             | 
| 148 217 | 
             
                    end
         | 
| 149 218 | 
             
                  end
         | 
| 150 219 | 
             
                end
         | 
| @@ -80,54 +80,69 @@ module Eco | |
| 80 80 | 
             
                      # @param data [Array<Hash>] data to be parsed. It cannot be used alongside with `file:`
         | 
| 81 81 | 
             
                      # @param file [String] absolute or relative path to the input file. It cannot be used alongside with `data:`.
         | 
| 82 82 | 
             
                      # @param format [Symbol] it must be used when you use the option `file:` (i.e. `:xml`, `:csv`), as it specifies the format of the input `file:`.
         | 
| 83 | 
            -
                      # @param  | 
| 83 | 
            +
                      # @param options [Hash] further options.
         | 
| 84 | 
            +
                      # @option options [String] :encoding optional parameter to read `file:` by expecting certain encoding.
         | 
| 85 | 
            +
                      # @option options [Boolean] :check_headers signals if the `csv` file headers should be expected.
         | 
| 84 86 | 
             
                      # @return [Eco::API::Common::People::Entries] collection of `Eco::API::Common::People::PersonEntry`.
         | 
| 85 | 
            -
                      def entries(data: (no_data = true; nil), file: (no_file = true; nil), format: (no_format = true; nil),  | 
| 87 | 
            +
                      def entries(data: (no_data = true; nil), file: (no_file = true; nil), format: (no_format = true; nil), **options)
         | 
| 86 88 | 
             
                        fatal("You should at least use data: or file:, but not both") if no_data == no_file
         | 
| 87 89 | 
             
                        fatal("You must specify a valid format: (symbol) when you use file.") if file && no_format
         | 
| 88 90 | 
             
                        fatal("Format should be a Symbol. Given '#{format}'") if format && !format.is_a?(Symbol)
         | 
| 89 91 | 
             
                        fatal("There is no parser/serializer for format ':#{format.to_s}'") unless no_format || @person_parser.defined?(format)
         | 
| 90 92 |  | 
| 91 | 
            -
                         | 
| 92 | 
            -
                         | 
| 93 | 
            -
                         | 
| 94 | 
            -
                        kargs.merge!(format:   format)   unless no_format
         | 
| 95 | 
            -
                        kargs.merge!(encoding: encoding) if encoding
         | 
| 93 | 
            +
                        options.merge!(content:  data)     unless no_data
         | 
| 94 | 
            +
                        options.merge!(file:     file)     unless no_file
         | 
| 95 | 
            +
                        options.merge!(format:   format)   unless no_format
         | 
| 96 96 |  | 
| 97 | 
            -
                        Entries.new(to_array_of_hashes(** | 
| 97 | 
            +
                        Entries.new(to_array_of_hashes(**options), klass: PersonEntry, factory: self)
         | 
| 98 98 | 
             
                      end
         | 
| 99 99 |  | 
| 100 100 | 
             
                      def to_array_of_hashes(**kargs)
         | 
| 101 101 | 
             
                        data = []
         | 
| 102 102 | 
             
                        content, file, encoding, format = kargs.values_at(:content, :file, :encoding, :format)
         | 
| 103 103 |  | 
| 104 | 
            -
                         | 
| 104 | 
            +
                        # Support for multiple file
         | 
| 105 | 
            +
                        if file.is_a?(Array)
         | 
| 106 | 
            +
                          return file.each_with_object([]) do |f, out|
         | 
| 107 | 
            +
                            logger.info("Parsing file '#{f}'")
         | 
| 108 | 
            +
                            curr = to_array_of_hashes(**kargs.merge(file: f))
         | 
| 109 | 
            +
                            out.concat(curr)
         | 
| 110 | 
            +
                          end
         | 
| 111 | 
            +
                        end
         | 
| 112 | 
            +
                        # Get content only when it's not :xls
         | 
| 113 | 
            +
                        # note: even if content was provided, file takes precedence
         | 
| 114 | 
            +
                        content = get_file_content(file, format, encoding) if (format != :xls) && file
         | 
| 105 115 |  | 
| 106 116 | 
             
                        case content
         | 
| 107 | 
            -
                        when !content
         | 
| 108 | 
            -
                          logger.error("Could not obtain any data out of these: #{kargs}")
         | 
| 109 | 
            -
                          exit(1)
         | 
| 110 117 | 
             
                        when Hash
         | 
| 111 118 | 
             
                          logger.error("Input data as 'Hash' not supported. Expecting 'Enumerable' or 'String'")
         | 
| 112 119 | 
             
                          exit(1)
         | 
| 113 120 | 
             
                        when String
         | 
| 114 | 
            -
                           | 
| 115 | 
            -
             | 
| 116 | 
            -
                            entry_hash.tap {|hash| hash["idx"] = j}
         | 
| 117 | 
            -
                          end
         | 
| 118 | 
            -
                          to_array_of_hashes(content: data)
         | 
| 121 | 
            +
                          deps = {check_headers: true} if kargs[:check_headers]
         | 
| 122 | 
            +
                          to_array_of_hashes(content: person_parser.parse(format, content, deps: deps || {}))
         | 
| 119 123 | 
             
                        when Enumerable
         | 
| 120 124 | 
             
                          sample = content.to_a.first
         | 
| 121 125 | 
             
                          case sample
         | 
| 122 126 | 
             
                          when Hash, Array, ::CSV::Row
         | 
| 123 127 | 
             
                            Eco::CSV::Table.new(content).to_array_of_hashes
         | 
| 124 128 | 
             
                          else
         | 
| 125 | 
            -
                            logger.error("Input 'Array' of '#{sample.class}' is not supported.")
         | 
| 129 | 
            +
                            logger.error("Input content 'Array' of '#{sample.class}' is not supported.")
         | 
| 126 130 | 
             
                          end
         | 
| 127 131 | 
             
                        else
         | 
| 128 | 
            -
                           | 
| 129 | 
            -
             | 
| 132 | 
            +
                          if file && format == :xls
         | 
| 133 | 
            +
                            person_parser.parse(format, file)
         | 
| 134 | 
            +
                          else
         | 
| 135 | 
            +
                            logger.error("Could not obtain any data out of these: #{kargs}. Given content: '#{content.class}'")
         | 
| 136 | 
            +
                            exit(1)
         | 
| 137 | 
            +
                          end
         | 
| 138 | 
            +
                        end.tap do |out_array|
         | 
| 139 | 
            +
                          start_from_two = (format == :csv) || format == :xls
         | 
| 140 | 
            +
                          out_array.each_with_index do |entry_hash, i|
         | 
| 141 | 
            +
                            entry_hash["idx"] = start_from_two ? i + 2 : i + 1
         | 
| 142 | 
            +
                            entry_hash["source_file"] = file
         | 
| 143 | 
            +
                          end
         | 
| 130 144 | 
             
                        end
         | 
| 145 | 
            +
             | 
| 131 146 | 
             
                      end
         | 
| 132 147 |  | 
| 133 148 |  | 
| @@ -150,7 +165,7 @@ module Eco | |
| 150 165 |  | 
| 151 166 | 
             
                        run = true
         | 
| 152 167 | 
             
                        if Eco::API::Common::Session::FileManager.file_exists?(file)
         | 
| 153 | 
            -
                          prompt_user(" | 
| 168 | 
            +
                          prompt_user("Do you want to overwrite it? (Y/n):", explanation: "The file '#{file}' already exists.", default: "Y") do |response|
         | 
| 154 169 | 
             
                            run = (response == "") || reponse.upcase.start_with?("Y")
         | 
| 155 170 | 
             
                          end
         | 
| 156 171 | 
             
                        end
         | 
| @@ -6,6 +6,14 @@ module Eco | |
| 6 6 | 
             
                    # Class to define a parser/serializer.
         | 
| 7 7 | 
             
                    class PersonAttributeParser < Eco::Language::Models::ParserSerializer
         | 
| 8 8 |  | 
| 9 | 
            +
                      # @note
         | 
| 10 | 
            +
                      #   - This was introduced at a later stage and might not be available for certain org-parsers configs
         | 
| 11 | 
            +
                      # @return [RequiredAttrs]
         | 
| 12 | 
            +
                      def required_attrs
         | 
| 13 | 
            +
                        @required_attrs ||= @dependencies[:required_attrs]
         | 
| 14 | 
            +
                        #@required_attrs ||= RequiredAttrs.new(attr, :unkown, [attr])
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
             | 
| 9 17 | 
             
                      # @see Eco::Language::Models::ParserSerializer#def_parser
         | 
| 10 18 | 
             
                      # @note
         | 
| 11 19 | 
             
                      #  - additionally, you can declare a callback `active:` to determine if when the
         | 
| @@ -10,7 +10,7 @@ module Eco | |
| 10 10 |  | 
| 11 11 | 
             
                      attr_reader :schema, :schema_attrs
         | 
| 12 12 |  | 
| 13 | 
            -
                      def initialize(person:  | 
| 13 | 
            +
                      def initialize(person: nil, schema: {}, account: {}, modifier: Common::People::PersonModifier.new)
         | 
| 14 14 | 
             
                        @modifier = Common::People::PersonModifier.new(modifier)
         | 
| 15 15 | 
             
                        @person  = person
         | 
| 16 16 | 
             
                        @account = account
         | 
| @@ -77,7 +77,9 @@ module Eco | |
| 77 77 | 
             
                        when Hash
         | 
| 78 78 | 
             
                          JSON.parse(person.to_json)
         | 
| 79 79 | 
             
                        else
         | 
| 80 | 
            -
                          { | 
| 80 | 
            +
                          {
         | 
| 81 | 
            +
                            "subordinates" => 0
         | 
| 82 | 
            +
                          }
         | 
| 81 83 | 
             
                        end
         | 
| 82 84 | 
             
                      end
         | 
| 83 85 |  | 
| @@ -16,7 +16,7 @@ module Eco | |
| 16 16 | 
             
                      CORE_ATTRS    = ["id", "external_id", "email", "name", "supervisor_id", "filter_tags", "freemium"]
         | 
| 17 17 | 
             
                      ACCOUNT_ATTRS = ["policy_group_ids", "default_tag", "send_invites", "landing_page_id", "login_provider_ids"]
         | 
| 18 18 | 
             
                      TYPE          = [:select, :text, :date, :number, :phone_number, :boolean, :multiple]
         | 
| 19 | 
            -
                      FORMAT        = [:csv, :xml, :json]
         | 
| 19 | 
            +
                      FORMAT        = [:csv, :xml, :json, :xls]
         | 
| 20 20 |  | 
| 21 21 | 
             
                      attr_reader :schema
         | 
| 22 22 | 
             
                      attr_reader :details_attrs, :all_model_attrs
         | 
| @@ -59,9 +59,15 @@ module Eco | |
| 59 59 |  | 
| 60 60 | 
             
                      # @!group Scopping attributes (identifying, presence & active)
         | 
| 61 61 |  | 
| 62 | 
            +
                      # @return [Array<Eco::API::Common::Loaders::Parser::RequiredAttrs>]
         | 
| 63 | 
            +
                      def required_attrs
         | 
| 64 | 
            +
                        @parsers.values_at(*all_attrs(include_defined_parsers: true)).compact.map(&:required_attrs).compact
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
             | 
| 62 67 | 
             
                      # All the internal name attributes, including _core_, _account_ and _details_.
         | 
| 63 68 | 
             
                      def all_attrs(include_defined_parsers: false)
         | 
| 64 | 
            -
                        all_model_attrs | defined_model_attrs
         | 
| 69 | 
            +
                        return all_model_attrs | defined_model_attrs if include_defined_parsers
         | 
| 70 | 
            +
                        all_model_attrs
         | 
| 65 71 | 
             
                      end
         | 
| 66 72 |  | 
| 67 73 | 
             
                      # Scopes `source_attrs` using the _**core** attributes_.
         | 
| @@ -15,7 +15,7 @@ module Eco | |
| 15 15 | 
             
                        # Reorders as follows:
         | 
| 16 16 | 
             
                        #   1. supervisors, people with no supervisor or where their supervisor not present
         | 
| 17 17 | 
             
                        #   2. subordinates
         | 
| 18 | 
            -
                        # @return [Array< | 
| 18 | 
            +
                        # @return [Array<PersonEntry>] `values` sorted by supervisors/subordinates
         | 
| 19 19 | 
             
                        def sort_by_supervisors(values, supervisors_first: true)
         | 
| 20 20 | 
             
                          raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash)
         | 
| 21 21 | 
             
                          return [] unless values && values.is_a?(Enumerable)
         | 
| @@ -5,14 +5,6 @@ module Ecoportal | |
| 5 5 | 
             
                  class Person
         | 
| 6 6 | 
             
                    attr_accessor :entry
         | 
| 7 7 |  | 
| 8 | 
            -
                    def reset_details!
         | 
| 9 | 
            -
                      doc["details"] = JSON.parse(original_doc["details"])
         | 
| 10 | 
            -
                    end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                    def consolidate_details!
         | 
| 13 | 
            -
                      original_doc["details"] = JSON.parse(doc["details"])
         | 
| 14 | 
            -
                    end
         | 
| 15 | 
            -
             | 
| 16 8 | 
             
                    def identify(section = :person)
         | 
| 17 9 | 
             
                      if entry && section == :entry
         | 
| 18 10 | 
             
                        entry.to_s(:identify)
         | 
| @@ -3,14 +3,6 @@ module Ecoportal | |
| 3 3 | 
             
                class Internal
         | 
| 4 4 | 
             
                  class Person
         | 
| 5 5 |  | 
| 6 | 
            -
                    def reset_account!
         | 
| 7 | 
            -
                      doc["account"] = JSON.parse(original_doc["account"])
         | 
| 8 | 
            -
                    end
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                    def consolidate_account!
         | 
| 11 | 
            -
                      original_doc["account"] = JSON.parse(doc["account"])
         | 
| 12 | 
            -
                    end
         | 
| 13 | 
            -
             | 
| 14 6 | 
             
                    def new?(doc = :initial)
         | 
| 15 7 | 
             
                      ref_doc = (doc == :original) ? original_doc : initial_doc
         | 
| 16 8 | 
             
                      !ref_doc["details"] && !ref_doc["account"]
         | 
| @@ -12,9 +12,11 @@ module Eco | |
| 12 12 | 
             
                    unless options.dig(:exclude, :core) && !person.new?
         | 
| 13 13 | 
             
                      micro.set_core(entry, person, options)
         | 
| 14 14 | 
             
                      if entry.supervisor_id?
         | 
| 15 | 
            -
                        micro.set_supervisor(entry.supervisor_id,  | 
| 15 | 
            +
                        micro.set_supervisor(person, entry.supervisor_id, people, options) do |unknown_id|
         | 
| 16 16 | 
             
                          # delay setting supervisor if does not exit
         | 
| 17 | 
            -
                          supers_job.add(person)  | 
| 17 | 
            +
                          supers_job.add(person) do |person|
         | 
| 18 | 
            +
                            micro.set_supervisor(person, unknown_id, people, options)
         | 
| 19 | 
            +
                          end
         | 
| 18 20 | 
             
                        end
         | 
| 19 21 | 
             
                      end
         | 
| 20 22 | 
             
                    end
         | 
| @@ -1,22 +1,27 @@ | |
| 1 1 | 
             
            module Eco
         | 
| 2 2 | 
             
              module API
         | 
| 3 3 | 
             
                class MicroCases
         | 
| 4 | 
            -
                  #  | 
| 5 | 
            -
                  # @note delaying the setting of a `supervisor_id` can save errors when the supervisor still does not exit.
         | 
| 6 | 
            -
                  # @param sup_id [nil, String] the **supervisor id** we should set on the `person`.
         | 
| 4 | 
            +
                  # Unique access point to set the `supervisor_id` value on a person.
         | 
| 7 5 | 
             
                  # @param person [Ecoportal::API::V1::Person] the person we want to update, carrying the changes to be done.
         | 
| 8 | 
            -
                  # @param  | 
| 6 | 
            +
                  # @param sup_id [nil, String] the **supervisor id** we should set on the `person`.
         | 
| 7 | 
            +
                  # @param people [Eco::API::Organization::People] _People_ involved in the current update.
         | 
| 9 8 | 
             
                  # @param options [Hash] the options.
         | 
| 10 9 | 
             
                  # @yield [supervisor_id] callback when the supervisor_id is **unknown** (not `nil` nor any one's in `people`).
         | 
| 11 10 | 
             
                  # @yieldparam supervisor_id [String] the **unknown** `supervisor_id`.
         | 
| 12 | 
            -
                  def set_supervisor( | 
| 11 | 
            +
                  def set_supervisor(person, sup_id, people, options)
         | 
| 13 12 | 
             
                    unless options.dig(:exclude, :core) || options.dig(:exclude, :supervisor)
         | 
| 14 | 
            -
                       | 
| 13 | 
            +
                      cur_id    = person.supervisor_id
         | 
| 14 | 
            +
                      cur_super = cur_id && with_supervisor(cur_id, people)
         | 
| 15 | 
            +
                      micro.with_supervisor(sup_id, people) do |new_super|
         | 
| 15 16 | 
             
                        if !sup_id
         | 
| 16 17 | 
             
                          person.supervisor_id = nil
         | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 18 | 
            +
                          descrease_subordinates(cur_super)
         | 
| 19 | 
            +
                        elsif new_super && id = new_super.id
         | 
| 20 | 
            +
                          person.supervisor_id = id
         | 
| 21 | 
            +
                          descrease_subordinates(cur_super)
         | 
| 22 | 
            +
                          increase_subordinates(new_super)
         | 
| 19 23 | 
             
                        elsif !block_given?
         | 
| 24 | 
            +
                          descrease_subordinates(cur_super)
         | 
| 20 25 | 
             
                          person.supervisor_id = sup_id
         | 
| 21 26 | 
             
                        else
         | 
| 22 27 | 
             
                          yield(sup_id) if block_given?
         | 
| @@ -25,6 +30,22 @@ module Eco | |
| 25 30 | 
             
                    end
         | 
| 26 31 | 
             
                  end
         | 
| 27 32 |  | 
| 33 | 
            +
                  private
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def descrease_subordinates(person, by = 1)
         | 
| 36 | 
            +
                    if person.is_a?(Ecoportal::API::V1::Person)
         | 
| 37 | 
            +
                      person.subordinates -= by
         | 
| 38 | 
            +
                      #person.subordinates  = 0 if person.subordinates < 0
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def increase_subordinates(person, by = 1)
         | 
| 43 | 
            +
                    if person.is_a?(Ecoportal::API::V1::Person)
         | 
| 44 | 
            +
                      #person.subordinates  = 0 if person.subordinates < 0
         | 
| 45 | 
            +
                      person.subordinates += by
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 28 49 | 
             
                end
         | 
| 29 50 | 
             
              end
         | 
| 30 51 | 
             
            end
         |