MiraitSystems/enju_trunk

View on GitHub
app/models/checkout_statistics.rb

Summary

Maintainability
F
3 days
Test Coverage
# encoding: utf-8
class CheckoutStatistics
  include ActiveModel::Conversion
  include ActiveModel::Validations
  extend ActiveModel::Naming
  extend ActiveModel::Translation

  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value) rescue nil
    end
  end
  def persisted? ; false ; end
  
  attr_accessor :statistical_table_type, :checked_at_from, :checked_at_to, :aggregation_type, :classification_type_id, :first_aggregation, :second_aggregation
                

  validates_presence_of :checked_at_from
  validates_presence_of :checked_at_to
  validates_presence_of :aggregation_type
  validate :checked_at_from_valid?
  validate :checked_at_to_valid?
  validate :checked_at_range_valid?
  
  def self.first_aggregations
    return [[I18n.t('activerecord.models.user_group'), 'user_group']]
  end
  
  def self.second_aggregations
    return [[I18n.t('activerecord.attributes.agent.grade'), 'grade']]
  end

  def make_data
    # 集計日付を求める
    if md = self.checked_at_from.match(/^([0-9]{4})-([0-9]{2})$/)
      checked_at_from = "#{md[1]}-#{md[2]}-01"
    else
      checked_at_from = self.checked_at_from
    end
    if md = self.checked_at_to.match(/^([0-9]{4})-([0-9]{2})$/)
      day = Date.new(md[1].to_i, md[2].to_i, 1).end_of_month
      checked_at_to = day.to_s
    else
      checked_at_to = self.checked_at_to
    end
    
    # 集計方法1
    if self.aggregation_type == "month"
      # 月別
      months = []
      from_date_obj = Date.parse(checked_at_from)
      to_date_obj = Date.parse(checked_at_to)
      # 集計月を求める
      if from_date_obj.mon == to_date_obj.mon
        months << {:display_name => from_date_obj.strftime("%Y-%m"), :from_date => from_date_obj.beginning_of_day, :to_date => to_date_obj.end_of_day}
      else
        if from_date_obj.day != 1
          months << {:display_name => from_date_obj.strftime("%Y-%m"), :from_date => from_date_obj.beginning_of_day, :to_date => from_date_obj.end_of_month.end_of_day}
        end
        (from_date_obj..to_date_obj).each{|i|
            next if i.day != 1
            if from_date_obj.year == i.year && from_date_obj.month == i.month
              from_date = from_date_obj.beginning_of_day
            else
              from_date = i.beginning_of_day
            end
            if to_date_obj.year == i.year && to_date_obj.month == i.month
              to_date = to_date_obj.end_of_day
            else
              to_date = Date.new(i.year, i.month, -1).end_of_day
            end
            months << {:display_name => i.strftime("%Y-%m"), :from_date => from_date, :to_date => to_date}
        }
      end
      cols = months
    elsif self.aggregation_type == "classification_type"
      # 分類別
      groups = Classification.select("group_identifier").where("classification_type_id = ?", self.classification_type_id).group("group_identifier")
      classification_groups = []
      groups.each do |group|
        if group.group_identifier.present?
          classification_ids = Classification.where("group_identifier = ?", group.group_identifier).pluck(:id)
        else
          # グループ未設定分
          classification_ids = Classification.where("group_identifier is null").pluck(:id)
        end
        classification_groups << {:display_name => group.group_identifier, :classification_ids => classification_ids}
      end
      cols = classification_groups
    end
    # 集計方法2
    if self.first_aggregation == "user_group"
      first_aggregation_column = "users.user_group_id"
      first_aggregations = UserGroup.all
    end
    if self.second_aggregation == "grade"
      second_aggregation_column = "agents.grade_id"
      second_aggregations =  Keycode.where(:name => 'agent.grade')
    end
    
    data = []
    sum_books = Array.new(cols.length, 0)
    sum_persons = Array.new(cols.length, 0)

    checkouts = Checkout.joins(:user => :agent)
    checkouts = checkouts.joins(:item => {:manifestation => :manifestation_has_classifications})
    if self.aggregation_type == "classification_type"
      from_date_obj = Date.parse(checked_at_from)
      to_date_obj = Date.parse(checked_at_to)
      checkouts = checkouts.where("checked_at >= ? and checked_at <= ?", from_date_obj.beginning_of_day, to_date_obj.end_of_day)
    end
    
    first_aggregations.each do |first_aggregation|
      first_checkouts = checkouts.where("#{first_aggregation_column} = ?", first_aggregation.id)
      details = []
      second_aggregations.each do |second_aggregation|
        if User.joins(:agent).where("#{first_aggregation_column} = ?", first_aggregation.id).where("#{second_aggregation_column} = ?", second_aggregation.id).present?
          second_checkouts = first_checkouts.where("#{second_aggregation_column} = ?", second_aggregation.id)
          # 冊数
          books = []
          sum_book = 0
          if self.aggregation_type == "month"
            months.each do |month|
              book = second_checkouts.select("checkouts.id").where("checked_at >= ? and checked_at <= ?", month[:from_date], month[:to_date]).reorder("checkouts.id").uniq
              books << book.length
            end
          elsif self.aggregation_type == "classification_type"
            classification_groups.each do |classification_group|
              book = second_checkouts.select("checkouts.id").where("manifestation_has_classifications.classification_id in (?)", classification_group[:classification_ids]).reorder("checkouts.id").uniq
              books << book.length
            end
          end
          # 人数
          persons = []
          sum_person = 0
          if self.aggregation_type == "month"
            months.each do |month|
              person = second_checkouts.select("checkouts.user_id").where("checked_at >= ? and checked_at <= ?", month[:from_date], month[:to_date]).reorder(:user_id).uniq
              persons << person.length
            end
          elsif self.aggregation_type == "classification_type"
            classification_groups.each do |classification_group|
              person = second_checkouts.select("checkouts.user_id").where("manifestation_has_classifications.classification_id in (?)", classification_group[:classification_ids]).reorder(:user_id).uniq
              persons << person.length
            end
          end
          details << {:second_aggregation_name => second_aggregation.keyname, :books => books, :persons => persons}
        end
      end
      # 学年未設定分
      if User.joins(:agent).where("#{first_aggregation_column} = ?", first_aggregation.id).where("#{second_aggregation_column} is null").present?
        second_checkouts = first_checkouts.where("#{second_aggregation_column} is null")
        # 冊数
        books = []
        sum_book = 0
        if self.aggregation_type == "month"
          months.each do |month|
            book = second_checkouts.select("checkouts.id").where("checked_at >= ? and checked_at <= ?", month[:from_date], month[:to_date]).reorder("checkouts.id").uniq
            books << book.length
          end
        elsif self.aggregation_type == "classification_type"
          classification_groups.each do |classification_group|
            book = second_checkouts.select("checkouts.id").where("manifestation_has_classifications.classification_id in (?)", classification_group[:classification_ids]).reorder("checkouts.id").uniq
            books << book.length
          end
        end
        # 人数
        persons = []
        sum_person = 0
        if self.aggregation_type == "month"
          months.each do |month|
            person = second_checkouts.select("checkouts.user_id").where("checked_at >= ? and checked_at <= ?", month[:from_date], month[:to_date]).reorder(:user_id).uniq
            persons << person.length
          end
        elsif self.aggregation_type == "classification_type"
          classification_groups.each do |classification_group|
              person = second_checkouts.select("checkouts.user_id").where("manifestation_has_classifications.classification_id in (?)", classification_group[:classification_ids]).reorder(:user_id).uniq
            persons << person.length
          end
        end
        details << {:second_aggregation_name => "", :books => books, :persons => persons}
      end
      
      # 小計
      books = Array.new(cols.length, 0)
      persons = Array.new(cols.length, 0)
      details.each do |detail|
        cols.each_with_index do |col, index|
          books[index] += detail[:books][index]
          persons[index] += detail[:persons][index]
          sum_books[index] += detail[:books][index]
          sum_persons[index] += detail[:persons][index]
        end
      end
      # 合計の出力は、第2集計が複数の場合のみとする
      if details.length > 1
        details << {:second_aggregation_name => I18n.t('statistical_table.subtotal'), :books => books, :persons => persons}
      end
      data << {:first_aggregation_name => first_aggregation.display_name, :details => details}
    end
    # 総合計
    data << {:first_aggregation_name => I18n.t('statistical_table.total'), :details => [{:second_aggregation_name => "", :books => sum_books, :persons => sum_persons}]}

    conditions = self
    conditions.checked_at_from = checked_at_from
    conditions.checked_at_to = checked_at_to
    return {:data => data, :cols => cols, :conditions => conditions}
  end

  def self.output_excelx(output_data, filename = nil)
    require 'axlsx'

    # initialize
    if filename.present?
      # 定期作成
      out_dir = "#{Rails.root}/private/system/checkout_statistics"
      excel_filepath = "#{out_dir}/list#{Time.now.strftime('%s')}#{filename}.xlsx"
    else
      # 手動作成
      out_dir = "#{Rails.root}/private/user_file/checkout_statistics"
      excel_filepath = "#{out_dir}/list#{Time.now.strftime('%s')}#{rand(10)}.xlsx"
    end
    FileUtils.mkdir_p(out_dir) unless FileTest.exist?(out_dir)

    Rails.logger.info "output_checkout_statistics_excelx filepath=#{excel_filepath}"

    Axlsx::Package.new do |p|
      wb = p.workbook
      wb.styles do |s|
        sheet = wb.add_worksheet(:name => I18n.t('activemodel.models.checkout_statistics'))
        top_style = s.add_style :font_name => Setting.checkout_statistics_print_excelx.fontname, :sz => 16, :alignment => {:horizontal => :center, :vertical => :center}
        default_style = s.add_style :font_name => Setting.checkout_statistics_print_excelx.fontname
        # ヘッダ
        sheet.add_row [I18n.t('activemodel.models.checkout_statistics')], :types => :string, :style => top_style
        sheet.merge_cells "A1:O1"
        condition = []
        condition << I18n.t('statistical_table.checkout_period') + ":"
        condition << output_data[:conditions].checked_at_from
        condition << "~"
        condition << output_data[:conditions].checked_at_to
        condition << ""
        condition << I18n.t('activemodel.attributes.checkout_statistics.aggregation_type') + ":"
        condition << I18n.t("statistical_table.checkout_statistics.aggregation_type.#{output_data[:conditions].aggregation_type}")
        if output_data[:conditions].aggregation_type == "classification_type"
          classification_type = ClassificationType.where("id = ?", output_data[:conditions].classification_type_id).first
          condition << classification_type.display_name
        else
          condition << ""
        end
        condition << ""
        condition << I18n.t('activemodel.attributes.checkout_statistics.first_aggregation') + ":"
        condition << I18n.t("statistical_table.checkout_statistics.first_aggregation.#{output_data[:conditions].first_aggregation}")
        condition << I18n.t("statistical_table.checkout_statistics.second_aggregation.#{output_data[:conditions].second_aggregation}")
        condition << ""
        condition << I18n.t('statistical_table.output_date') + ":"
        condition << Date.today
        sheet.add_row condition, :types => :string, :style => default_style
        
        # 項目名
        columns = [I18n.t('activerecord.attributes.user.user_group'), I18n.t('activerecord.attributes.agent.grade'), ""]
        output_data[:cols].each do |col|
          columns << col[:display_name]
        end
        columns << I18n.t('statistical_table.total')
        sheet.add_row columns, :types => :string, :style => default_style
        
        # データ
        output_data[:data].each do |datum|
          datum[:details].each_with_index do |detail, index|
            # 冊数
            if index == 0
              books_row = [datum[:first_aggregation_name]]
            else
              books_row = [""]
            end
            books_row << detail[:second_aggregation_name]
            books_row << I18n.t('statistical_table.books')
            books_row += detail[:books]
            books_row << detail[:books].inject(:+)
            sheet.add_row books_row, :types => :string, :style => default_style
            # 人数
            persons_row = ["", ""]
            persons_row << I18n.t('statistical_table.persons')
            persons_row += detail[:persons]
            persons_row << detail[:persons].inject(:+)
            sheet.add_row persons_row, :types => :string, :style => default_style
          end
        end
        p.serialize(excel_filepath)
      end
    end
    return excel_filepath
  end

private

  def checked_at_from_valid?
    if checked_at_from.present?
      unless /^[0-9]{4}-[0-9]{2}$/ =~ checked_at_from || /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ =~ checked_at_from
        errors.add(:checked_at_from)
      end
    end
  end

  def checked_at_to_valid?
    if checked_at_to.present?
      unless /^[0-9]{4}-[0-9]{2}$/ =~ checked_at_to || /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ =~ checked_at_to
        errors.add(:checked_at_to)
      end
    end
  end

  def checked_at_range_valid?
    if checked_at_from.present? && checked_at_to.present?
      if (/^[0-9]{4}-[0-9]{2}$/ =~ checked_at_from && /^[0-9]{4}-[0-9]{2}$/ =~ checked_at_to) || (/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ =~ checked_at_from && /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ =~ checked_at_to)
      
        if checked_at_from > checked_at_to
          errors.add(:checked_at_from)
        end
      else
        errors.add(:checked_at_from)
      end
    end
  end

end