#= 単価更新
#
module CommLogistics::Modules::MngProductsPrice
  require 'config/site_config'
  include CommLogistics::Const::Code
  include CommLogistics::Const::Error
  
  #==== 平均系親クラス
  class ProductsPriceAve < ProductsPrice
    #== 指定日から更新対象のレコード群を取得
    #[product_id] 製品ID
    #[target_date] 対象日付
    def self.series(product_id, target_date, is_purchase=false)
      #仕入れの場合は当日を含む
      grater_str = is_purchase ? '>=' : '>'
      result = find_by_sql(["SELECT * FROM products_prices 
                             WHERE target_date #{grater_str} ? AND product_id = ?
                             ORDER BY target_date ASC",
                             target_date, product_id])
      #logger.debug(["products_price series", result.inspect].join("\n\t"))
      return result
    end
    
    #== 単価更新
    #[last_quantity] 期間前の在庫量
    def update_price(last_quantity, product_price_calc_code)
      self.stock_price_type_code = product_price_calc_code
      pcd = PurchaseDetail.summed_prices(self.product_id, @start_term, @end_term)
      if pcd
        summed_price = pcd.summed_price.to_f
        summed_quantity = pcd.summed_quantity.to_i
      else
        summed_price = 0
        summed_quantity = 0
      end
      
      pp = ProductsPrice.last_one(self.product_id, @start_term)
      if pp
        new_total = pp.price * last_quantity + summed_price
      else
        new_total = summed_price
      end
      new_quantity = last_quantity + summed_quantity
      
      logger.debug(["products_price update_price", 
                    "last_quantity:"+last_quantity.inspect, 
                    "pp:"+pp.inspect, 
                    "summed_price:"+summed_price.inspect,
                    "summed_quantity:"+summed_quantity.inspect].join("\n\t"))
      if new_quantity != 0
        new_price = new_total / new_quantity
      else
        new_price = 0
      end
      self.price = new_price
      self.save
      logger.debug(["products_price new_price", new_price].join("\n\t"))
      return new_price
    end
  end
  
  #移動平均
  #
  # 現在個数 * 原単価 + 仕入れ数 * 仕入れ単価
  # -----------------------------------
  #        現在個数 + 仕入れ数
  #
  class ProductsPriceMovingAve < ProductsPriceAve
    def update_price(last_quantity, product_price_calc_code)
      @start_term = self.target_date
      @end_term = self.target_date
      super
    end
    
    def get_last_quantity(target_date, pid)
      #すぐ前の日付
      quantity = CommLogistics::Tools::StockCounter.get_quantity(get_last_date(target_date), pid)
      return quantity
    end
  end
  
  #月次総平均
  # 期首棚卸資産 + 期中取得棚卸
  # -------------------------
  #            総数量
  #
  # 前月末個数 * 前月末単価 + 今月仕入れ数 * 今月仕入れ単価
  # -----------------------------------
  #        前月末個数 + 今月仕入れ数
  #
  class ProductsPriceMonthlyAve < ProductsPriceAve
    def get_last_month_end_date(target_date)
      date = convert_to_date(target_date)
      date_prev_month = date.last_month
      return date_prev_month.end_of_month
    end
    
    def get_this_month_first_date(target_date)
      date = convert_to_date(target_date)
      return date.beginning_of_month
    end
    
    def get_this_month_end_date(target_date)
      date = convert_to_date(target_date)
      return date.end_of_month
    end
    
    def get_last_quantity(target_date, pid)
      #前月末在庫
      quantity = CommLogistics::Tools::StockCounter.get_quantity(get_last_month_end_date(target_date) , pid)
      return quantity
    end
    
    #== 単価更新
    #[last_quantity] 期間前の在庫量
    def update_price(last_quantity, product_price_calc_code)
      @start_term = get_this_month_first_date(self.target_date)
      @end_term = get_this_month_end_date(self.target_date)
      super
    end
  end
  
  #==== 原価系親クラス
  class ProductsPriceCost < ProductsPrice
    def self.series(product_id, target_date, is_purchase=false)
      if is_purchase
        #仕入れの場合は指定日レコード一つ
        result = find_by_sql(["SELECT * FROM products_prices 
                               WHERE target_date = ? AND product_id = ?",
                               target_date, product_id])
        logger.debug(["products_price series", result.inspect].join("\n\t"))
      else
        #仕入以外は更新なし
        result = nil
      end
      return result
    end
    
    #数量関係ないのでnilを返す
    def get_last_quantity(target_date, pid)
      return nil
    end
  end
  
  #最終仕入原価
  class PoductsPriceLastPurchase < ProductsPriceCost
    def self.need_recording?(opts)
      if opts[:sign_type_code] == MCODE_SIGN_TYPE_RED
        return false
      end
      return true
    end
    
    def update_price(last_quantity, product_price_calc_code)
      self.stock_price_type_code = product_price_calc_code
      pcd = PurchaseDetail.last_price(self.product_id, self.target_date)
      if pcd
        self.price = pcd.price
      else
        #みつからなかったら
        self.price = 0
      end
      self.save
      logger.debug(["products_price new_price", self.price].join("\n\t"))
      return self.price
    end
  end
  
  #標準原価
  class PoductsPriceStandardCost < ProductsPriceCost
    def update_price(last_quantity, product_price_calc_code)
      self.stock_price_type_code = product_price_calc_code
      pd = Product.find(self.product_id)
      if pd.cost_price
        self.price = pd.cost_price
      elsif pd.product_set_id
        #製品群で再検索
        pds = ProductSet.find(pd.product_set_id)
        if pds.cost_price
          self.price = pds.cost_price
        else
          raise UserOperationError, LOEMJ0011+LOEMD0013
        end
      else
        raise "product record invalid"
      end
      self.save
      logger.debug(["products_price new_price", self.price].join("\n\t"))
      return self.price
    end
  end
  
  def set_products_price_class(pp_calc_code)
    case pp_calc_code
    when PRODUCTS_PRICES_CALC_CODE_MOVING_AVE
      pp_class = ProductsPriceMovingAve
    when PRODUCTS_PRICES_CALC_CODE_MONTHLY_AVE
      pp_class = ProductsPriceMonthlyAve
    when PRODUCTS_PRICES_CALC_CODE_LAST_PURCHASE
      pp_class = PoductsPriceLastPurchase
    when PRODUCTS_PRICES_CALC_CODE_STANDARD_COST
      pp_class = PoductsPriceStandardCost
    else
      raise 'Unknown products price calc code : '+pp_calc_code.inspect
    end
    @product_price_class = pp_class
    logger.debug(["SET PRODUCTS PRICE", 
                  "PRODUCT PRICE CLASS:"+@product_price_class.inspect].join("\n\t"))
  end
  
  def get_products_price_class
    return @product_price_class
  end
  
  #==単価更新
  def update_products_price(main, details, calc_type, is_purchase=true, product_price_calc_code=$PRODUCTS_PRICES_CALC_CODE)
    #自インスタンスに単価クラスをセット
    set_products_price_class(product_price_calc_code)
    target_date = convert_to_date(get_value_by_name(main, :target_date))
    org_target_date = target_date
    #非在庫管理の製品では単価を更新しない
    #product_ids = details.collect{|detail| get_value_by_name(detail, :product_id)}.uniq
    tmp_product_ids = []
    details.each do |detail|
      #tmp_product_ids.push(get_value_by_name(detail, :product_id)) if detail['stock_flag_code'] == MCODE_STOCK_FLAG_ON
      #無形の製品についても、原価を記録する必要もあるので、制限をはずす。
      tmp_product_ids.push(get_value_by_name(detail, :product_id))
    end
    product_ids = tmp_product_ids.uniq
    if defined?(@refresh_price)
      #update
      if @refresh_price
        # update 2回目 
        # 古い方を更新開始対象の日付にする
        # 未ー＞完などの状態変更を伴う場合は1回目がないので存在をチェックしてから
        if @refresh_target_date && @refresh_product_ids
          if target_date > @refresh_target_date
            target_date = @refresh_target_date
          end
          # 更新対象IDを2処理の和で作る
          product_ids = @refresh_product_ids | product_ids
        end
      else
        # update 1回目
        # target_date, product_idを保存して終了
        @refresh_target_date = target_date
        @refresh_product_ids = product_ids
        return
      end
    end
    
    logger.debug(["update_products_price", 
                  "refresh target_date:"+target_date.inspect, 
                  "product_ids:"+product_ids.inspect].join("\n\t"))
    #falseの場合はpurchase扱いしない
    if is_purchase and !get_products_price_class.need_recording?({:sign_type_code=>get_value_by_name(main, :sign_type_code)})
      is_purchase = false
    end
    product_ids.each do |pid|
      # (仕入時のみ)org_target_dateのレコードを作成
      if is_purchase
        pp = get_products_price_class.one(pid, org_target_date)
        if pp.blank?
          pp = get_products_price_class.new({:product_id=>pid,
                                             :target_date=>org_target_date})
          pp.save!
        end
      end
      # target_dateより若いもの(仕入れの場合は当日を含む)を更新
      series = get_products_price_class.series(pid, target_date, is_purchase)
      unless series.blank?
        series.each do |sp|
          quantity = sp.get_last_quantity(sp.target_date, pid)
          logger.debug(["PRODUCTS PRICE", 
                        "PRODUCT :"+pid.inspect, 
                        "QUANTITY:"+quantity.inspect,
                        "PP_CODE :"+product_price_calc_code.inspect].join("\n\t"))
          sp.update_price(quantity, product_price_calc_code)
        end
      end
    end
  end
  
  #==加工入庫用インタフェース
  # 別の単価計算方法を使う
  def update_products_price_process_load(main, details, calc_type, is_purchase=true)
    if $PRODUCTS_PRICES_CALC_CODE_PROCESS_LOAD
      pp_calc_code = $PRODUCTS_PRICES_CALC_CODE_PROCESS_LOAD
    else
      pp_calc_code = $PRODUCTS_PRICES_CALC_CODE
    end
    update_products_price(main, details, calc_type, is_purchase, pp_calc_code)
  end
  
  def get_last_date(target_date)
    return convert_to_date(target_date, true)
  end
  
  def convert_to_date(target_date, need_last_date=false)
    if target_date.is_a?(Date)
      td = target_date
    else
      td = Date.parse(target_date)
    end
    if need_last_date
      td = td - 1
    end
    return td
  end
end
