class Shipping < CommLogistics::Base::Model::SuperiorStock
  include Comm::Module::Model::Logging
  include CommLogistics::Modules::WarningUtil
  include CommLogistics::Modules::SetParams
  include CommLogistics::Modules::StockSearch
  
  has_many :child_details,
           :class_name => 'ShippingDetail',
           :dependent => :destroy,
           :order => 'seq_number'
  
  def self.check_valid_quantities(product_id, warehouse_ids=nil, target_date=Date.today)
    require 'config/site_config'
    warehouse_ids = Warehouse.regular_ids unless warehouse_ids
    raise UserOperationError, '自社倉庫が設定されていません' if warehouse_ids.blank?
    raise 'product is null' unless product_id
    par = Product.find(product_id)
    diff_date = par.shipment_validity_period.to_i
    date_limit = (target_date + ($MONTH_VALIDITY_PERIOD ? (diff_date * 30) : diff_date)).to_s
    
    st = Stock.find_by_sql(["SELECT IFNULL(st.enough_quantity,0) - IFNULL(ss.valid_quantity,0) AS enough_span_quantity, 
                                    IFNULL(st.existing_quantity,0) - IFNULL(ss.valid_quantity, 0) AS all_span_quantity
                             FROM (
                               SELECT product_id,
                                 SUM(IF(ubd >= \"#{date_limit}\", existing_quantity, 0)) AS enough_quantity,
                                 SUM(existing_quantity) AS existing_quantity
                               FROM stocks AS tmp_s 
                               WHERE product_id=? AND warehouse_id IN (?)
                               GROUP BY product_id
                             ) AS st
                             RIGHT JOIN (
                               SELECT product_id,
                                      own_warehouse_id,
                                      SUM(quantity) AS valid_quantity 
                               FROM shippings AS s 
                               RIGHT JOIN shipping_details as sd ON s.id=sd.shipping_id 
                               WHERE invalid_flag_code=? AND shipping_state_code NOT IN (?) AND sd.product_id=? AND own_warehouse_id IN (?)
                               GROUP BY product_id
                             ) AS ss ON st.product_id=ss.product_id",
                             product_id, warehouse_ids, MCODE_FLAG_OFF, [MCODE_SHIPPING_STATE_WAIT, MCODE_SHIPPING_STATE_COMP, MCODE_SHIPPING_STATE_BO],
                             product_id, warehouse_ids])
                             
    Rails.logger.debug('ENOUGH QUANTITY : '+st.first['enough_span_quantity'])
    Rails.logger.debug('All QUANTITY : '+st.first['all_span_quantity'])
    return st.first
  end
  
  def self.picking_printed_search(params)
    delivery_id_str = params['delivery_id'] ? "delivery_id = #{params['delivery_id']}" : "delivery_id IS NULL"
    result = find_by_sql(["SELECT id FROM shippings
                           WHERE target_date = ? AND
                                 shipment_customer_id = ? AND
                                 #{delivery_id_str} AND
                                 invalid_flag_code = #{MCODE_FLAG_OFF} AND
                                 print_picking_date IS NOT NULL AND
                                 id != ?", 
                           params['target_date'],
                           params['shipment_customer_id'],
                           params['id']])
    return result
  end
  
  named_scope :picking_printed_own_search, lambda {|params| {
    :conditions => [
      "`print_picking_date` IS NOT NULL AND `id` = ?",
      params['id']
    ]
  }}
  
  def self.linkage_standerdize(record, link)
    link['state_code'] = record['shipping_state_code']
  end
  
  def self.get_comp_total_quantity(id, par=nil)
    ar = find(id, :select=>"total_quantity, shipping_state_code")
    return ar && ar.shipping_state_code==MCODE_SHIPPING_STATE_COMP ? ar.total_quantity : 0
  end
#  def self.collect_trucking_records(params)
#    if params[:start_target_date] != params[:end_target_date]
#      raise UserOperationError, '出荷日付の開始／終了を揃えてください。'
#    end
#    target_date = params[:start_target_date]
#    trucking_id = params[:trucking_id].strip.to_i
#    if params[:id_list].blank?
#      #一括出力 状態:完了のみ
#      select_adding = " , sp2.print_trucking_note_date AS last_printed_date "
#      from_adding =  " LEFT JOIN ( SELECT print_trucking_note_date, target_date, shipment_customer_id, delivery_id FROM shippings "
#      from_adding << " WHERE target_date=\'#{target_date}\' AND print_trucking_note_date IS NOT NULL ORDER BY print_trucking_note_date DESC) AS sp2"
#      from_adding << " ON sp.shipment_customer_id=sp2.shipment_customer_id AND IF(IFNULL(sp2.delivery_id, 1), sp2.delivery_id IS NULL, sp2.delivery_id=sp.delivery_id) "
#      where_adding =  " AND sp.print_trucking_note_date IS NULL "
#      where_adding << " AND sp.shipping_state_code = #{MCODE_SHIPPING_STATE_COMP} "
#    else
#      #個別出力 状態:完了+ピッキング待ち
#      select_adding = ''
#      from_adding = ''
#      where_adding =  " AND sp.id in (#{params[:id_list]}) "
#      where_adding << " AND (sp.shipping_state_code = #{MCODE_SHIPPING_STATE_COMP} OR sp.shipping_state_code = #{MCODE_SHIPPING_STATE_BEFORE_DIRECTION})"
#    end
#    ars = find_by_sql(["SELECT sp.* #{select_adding}
#                        FROM shippings AS sp
#                        LEFT JOIN master_app_production.customers AS cm 
#                             ON sp.shipment_customer_id=cm.id #{from_adding}
#                        WHERE sp.target_date = ? AND
#                              sp.trucking_id = ? AND
#                              sp.invalid_flag_code = #{MCODE_FLAG_OFF}
#                              #{where_adding}
#                        ORDER BY cm.kana_name, sp.id", 
#                        target_date,
#                        trucking_id])
#    if ars.blank?
#      raise UserOperationError, '印刷対象がありません'
#    end
#    Rails.logger.debug('PRINT ARS:'+ars.inspect)
#    return ars
#  end
  
  #Module::SetParamsをオーバーライド
  #出荷データ作成(自動フロー用)
  def set_table_params(parent_ar, params)
    table_params = super(parent_ar, params)
    tmp_customer_id = parent_ar.shipment_customer_id || parent_ar.customer_id || nil
    unless tmp_customer_id.blank?
      customer_ar = Customer.find(tmp_customer_id.to_i)
      if customer_ar 
        table_params[:shipping_sheet_type_code] = customer_ar.shipping_sheet_type_code
      end
    end
    add_own_warehouse_to_table_params(table_params, params)
    return table_params
  end
  
  def set_detail_params(parent_ar, table_params, params)
    require 'config/site_config.rb'
    detail_params = AcceptOrderDetail.find(:all, 
             :conditions=>["accept_order_id=? AND stock_flag_code=?",parent_ar.id, MCODE_STOCK_FLAG_ON], 
             :group => ($AUTO_FLOW_MERGE_LOT ? "product_id" : nil), 
             :select => "product_category_id, 
                         product_set_id, 
                         product_id, 
                         #{MCODE_STOCK_FLAG_ON} AS stock_flag_code, 
                         #{$AUTO_FLOW_MERGE_LOT ? 'SUM(quantity) AS ' : "" } quantity, 
                         #{$AUTO_FLOW_MERGE_LOT ? 'SUM(quantity) AS ' : 'quantity AS ' } scheduled_quantity").only_hashfy
    if $AUTO_FLOW_SKIP_LOT==true
      #在庫の有効数量計算をしないために空白を設定
      detail_params.each{|d| d['lot_number'] = '' unless d.include?('lot_number')}
    else
      stock_search_params = HashWithIndifferentAccess.new({:has_quantity=>"true", 
                             :no_merge=>"true", 
                             :only_shippable => ($AUTO_FLOW_NON_SHIPPABLE ? "false" : "true"), 
                             :warehouse_id=>table_params[:own_warehouse_id], 
                             :supplier_id=>table_params[:supplier_id], 
                             :original_warehouse_id => table_params[:warehouse_id],
                             :original_customer_id => table_params[:customer_id],
                             :product_id=>(detail_params.map{|item|item['product_id']}).uniq.join(','), 
                             :target_date=>table_params[:target_date]}).merge(params)
      #ロットを埋めないパターンでは、successはfalseとする。
      stocks = stock_search('lot_number', stock_search_params)
      set_number_to_detail(detail_params, stocks)
    end
    table_params[:shipping_state_code]= $AUTO_FLOW_SKIP_LOT==true ? ($AUTO_FLOW_SKIP_LOT_SHIPPING_STATE || MCODE_SHIPPING_STATE_WAIT) : MCODE_SHIPPING_STATE_COMP
    return detail_params
  end
  
  def set_number_to_detail(detail_params, stocks)
    products_lot_index = {}
    detail_params.each_with_index do |detail_rec, i|
      unless detail_rec['product_id'].blank? 
        product_id = detail_rec['product_id'].to_i
        scheduled = detail_rec['scheduled_quantity'].to_i
        start_j = products_lot_index[product_id] || 0
        for j in (start_j)..(stocks.length-1)
          lot_rec = stocks[j]
          if product_id==lot_rec['product_id'].to_i && lot_rec['available_quantity'].to_i > 0
            available = lot_rec['available_quantity'].to_i
            #一つのロットでは足りないとき。
            if available < scheduled 
              detail_rec['scheduled_quantity']=available;
              detail_rec['quantity']=available;
              #行を追加
              detail_params.insert(i+1, {'product_category_id'=>detail_rec['product_category_id'], 
                                         'product_set_id'=>detail_rec['product_set_id'], 
                                         'product_id'=>detail_rec['product_id'],
                                         'scheduled_quantity'=> scheduled - available,
                                         'quantity' => scheduled - available,
                                         'stock_flag_code' => detail_rec['stock_flag_code']})
              lot_rec['available_quantity']=0
            #ひとつのロットで足りるとき。
            else
              lot_rec['available_quantity'] = available - scheduled
            end
            detail_rec['lot_number']=lot_rec['lot_number']
            detail_rec['serial_number']=lot_rec['serial_number']
            detail_rec['location_number']=lot_rec['location_number']
            detail_rec['ubd']=lot_rec['ubd']
            #detail_rec['stock_type_code']=lot_rec['stock_type_code']
            detail_rec['validity_period']=lot_rec['validity_period']
            detail_rec['unit_code']=lot_rec['unit_code']
            products_lot_index[product_id] = lot_rec['available_quantity'] > 0 ? j : j+1;
            break
          end
        end
      end
    end
  end
  
  def create_not_auto_flow(params)
    params[:main]['shipping_state_code'] = MCODE_SHIPPING_STATE_WAIT
    create_exec_auto_flow(params)
  end
  def check_set_details(detail_params)
    super || $AUTO_FLOW_SKIP_LOT
  end
  def complete?
    if self.shipping_state_code and self.shipping_state_code == MCODE_SHIPPING_STATE_COMP
      true
    else
      false
    end
  end
protected
  def create_exec_do(main, details)
    self.attributes = main
    unless save
      raise EMJ0001 + EMD0001
    end
    update_details(self.id, details)
    check_adequate_stock(main, details)
    check_ubd_limit(main, details)
    update_stock_info(main, details)
    true
  end
  def create_exec_do_auto_flow(main, details)
    Rails.logger.debug("AF shipping\n"+main.inspect+"\n"+details.inspect)
    self.attributes = main
    unless save
      raise EMJ0001 + EMD0001
    end
    update_details(self.id, details)
    update_stock_info(main, details)
  end
  
  def destroy_exec_do
    details = child_details.ext_hashfy
    update_stock_info(self, details, false)
    true
  end
  
  def update_stock_info(main, details, is_create=true)
    require 'config/site_config'
    _shipping_state_code = get_value_by_name(main, 'shipping_state_code')
    #指示書が出力済みか調べる。$SHIPPING_CONTROL_ROLE_IDにroleのIDが入っていたら調べる
    $SHIPPING_CONTROL_ROLE_ID ? shipping_ctl_role_id = $SHIPPING_CONTROL_ROLE_ID : shipping_ctl_role_id = []
    unless shipping_ctl_role_id.blank? ||
           session[:role_ids] != (session[:role_ids] - shipping_ctl_role_id) || 
           session[:role_ids].include?(ROLE_ADMIN_ID) || 
           _shipping_state_code==MCODE_SHIPPING_STATE_WAIT || 
           _shipping_state_code==MCODE_SHIPPING_STATE_BEFORE_DIRECTION || 
           _shipping_state_code==MCODE_SHIPPING_STATE_BO || 
           get_value_by_name(main, 'shipping_type_code')==MCODE_SHIPPING_TYPE_MOVE
      raise UserOperationError, (EMJ0003 + LOEMD0019)
    end
    Rails.logger.debug("update_stock_info _shipping_state_code\n"+_shipping_state_code.inspect)    
    case _shipping_state_code
    when MCODE_SHIPPING_STATE_WAIT,
         MCODE_SHIPPING_STATE_BEFORE_DIRECTION,
         MCODE_SHIPPING_STATE_BEFORE_PICK,
         MCODE_SHIPPING_STATE_BEFORE_COURIERSLIP
      # sub available own storage stocks
      _own_warehouse_id = get_value_by_name(main, 'own_warehouse_id')
      Rails.logger.debug("update_stock_info _own_warehouse_id\n"+_own_warehouse_id.inspect)    
      unless _own_warehouse_id.blank?
        overwrite_params = {'warehouse_id' => _own_warehouse_id, 'customer_id' => nil}
        s_details = get_details_params_for_stock(main, details, overwrite_params)
        calc_type = is_create ? CALC_SUB : CALC_ADD
        update_own_stock(main, s_details, calc_type, false, true)
      end
    when MCODE_SHIPPING_STATE_COMP
      # sub existing own storage stocks
      _own_warehouse_id = get_value_by_name(main, 'own_warehouse_id')
      Rails.logger.debug("update_stock_info _own_warehouse_id\n"+_own_warehouse_id.inspect)    
      unless _own_warehouse_id.blank?
        overwrite_params = {'warehouse_id' => _own_warehouse_id, 'customer_id' => nil}
        s_details = get_details_params_for_stock(main, details, overwrite_params)
        calc_type = is_create ? CALC_SUB : CALC_ADD
        update_own_stock(main, s_details, calc_type)
      end
      
      # add out stocks
      calc_type = is_create ? CALC_ADD : CALC_SUB
      if own_warehouse?(main)
        s_details = get_details_params_for_stock(main, details)
        update_own_stock(main, s_details, calc_type, true)
      else
        s_details = get_details_params_for_stock(main, details)
        update_stock(main, s_details, calc_type)
      end
    end
  end
  
  def get_details_params_for_stock(main, details, overwrite_params={})
    results = []
    update_params = {}
    extract_lacking_params(update_params, main)
    update_params.update(overwrite_params)
    stock_type = get_value_by_name(main, 'shipping_type_code')
    if stock_type == MCODE_SHIPPING_TYPE_SHORT
      update_params['stock_type_code'] = MCODE_STOCK_TYPE_SHRT
    end
    #”完”以外はロットがはいってないものを省いて処理を許容する lot選択移行向け
    state = get_value_by_name(main, 'shipping_state_code')
    error_array=[]
    details.each do |params|
      #sales/purchase以外で非在庫管理製品があった場合はエラーを出力する
      unless params['stock_flag_code'] == MCODE_STOCK_FLAG_ON
        error_array << Product.find(params['product_id']).disp_name
        next
      end
      unless state == MCODE_SHIPPING_STATE_COMP
        detail_lot = get_value_by_name(params, 'lot_number')
        next if detail_lot == ''
      end
      
      new_params = Marshal.load(Marshal.dump(params))
      new_params.update(update_params)
      results.push(new_params)
    end
    unless error_array.blank?
      #エラー
      msg = LOEMJ0002+LOEMD0020+error_array.join(', ')
      raise UserOperationError, msg
    end
    results
  end
  
  NON_ARG_UBD='9999-00-00'
  def check_ubd_limit(main, details)
    target_date = Date.parse(get_value_by_name(main, 'target_date'))
    product_ids = details.collect{|detail| get_value_by_name(detail, 'product_id')}.uniq
    sv_hash = Product.get_shipment_validity_periods(product_ids, target_date)
    #today = Date.today
    ubd_out_array,shipment_out_array=[],[]
    details.each do |pd|
      ubd = get_value_by_name(pd, 'ubd')
      product_id = get_value_by_name(pd, 'product_id')
      #UBD未設定とUBDなしはチェックしない
      if !ubd or ubd == NON_ARG_UBD
        next
      end
      unless ubd.is_a?(Date)
        begin
          ubd = Date.parse(ubd)
        rescue ArgumentError
          #パースエラー '00'が末日の対応
          if ubd =~ /^\d\d\d\d(-|\/)\d\d(-|\/)00$/
            ubd_ary = ubd.split(/(-|\/)/)
            if ubd_ary.length == 5
              ubd = Date.new(ubd_ary[0].to_i, ubd_ary[2].to_i, -1)
              Rails.logger.info(' UBD replace 00:'+ubd.inspect)
            else
              raise 'unknow ubd format.. :'+ubd.inspect
            end
          else
            raise 'unknow ubd format.... :'+ubd.inspect
          end
        end
      end
      #期限切れ
      if ubd <= target_date #こちらは悲観的に期限日を含める
        ubd_out_array << Product.find(product_id).disp_name
      else
        #出荷期限切れ マスターに未設定はチェックしない
        sv = sv_hash[product_id]
        if sv and ubd < sv
          shipment_out_array << Product.find(product_id).disp_name
        end
      end
    end
    msg=''
    unless ubd_out_array.blank?
      msg = LOWMJ0002+LOWMJ0003+ubd_out_array.join(',')
    end
    unless shipment_out_array.blank?
      msg = msg.blank? ? LOWMJ0002+LOWMD0004+shipment_out_array.join(',') : msg+"<br>"+LOWMD0004+shipment_out_array.join(',')
    end
    unless msg.blank?
      #警告からのリトライ時は警告にしない
      unless is_retry_included?(WARNING_UBD_LIMIT)
        #警告(リトライ)
        make_retry_include(WARNING_UBD_LIMIT)
        msg = msg+"<br>"+LOWMD0005
        raise UserOperationError, msg
      end
    end
  end
  
  def check_adequate_stock(main, details)
    case get_value_by_name(main, 'shipping_state_code')
    when MCODE_SHIPPING_STATE_BEFORE_DIRECTION,
         MCODE_SHIPPING_STATE_BEFORE_PICK,
         MCODE_SHIPPING_STATE_BEFORE_COURIERSLIP
      # [未]と[BO](在庫０でも許容)、[完](在庫０だと遷移できないハズ)以外
      product_ids = details.collect{|detail| get_value_by_name(detail, 'product_id')}.uniq
      specified_own_warehouse_id = get_value_by_name(main, 'own_warehouse_id')
      target_date = get_value_by_name(main, 'target_date')
      retry_array,error_array=[],[]
      unless specified_own_warehouse_id.blank?
        product_ids.each do |pid|
          vq_ar = Shipping.check_valid_quantities(pid, specified_own_warehouse_id, Date.parse(target_date))
          if vq_ar['all_span_quantity'].to_i < 0
            if vq_ar['enough_span_quantity'].to_i < 0
              #全期間がエラーでUBD十分在庫もエラー -> エラー
              error_array << Product.find(pid).disp_name
            else
              #全期間がエラーでUBD十分在庫がOK -> ありえない
              raise 'ありえない状態'
            end
          elsif vq_ar['enough_span_quantity'].to_i < 0
            #UBD十分在庫だけがエラー -> 警告
            retry_array << Product.find(pid).disp_name
          end
        end
      end
      if !error_array.blank?
        #エラー
        msg = LOEMJ0002+LOEMD0012+error_array.join(', ')
        if !retry_array.blank?
          #（もしエラーがなければ）リトライの型番も情報として付け足す
          msg += "<br>"+LOWMD0002+retry_array.join(', ')
        end
        raise UserOperationError, msg
      elsif !retry_array.blank?
        #警告からのリトライ時は警告にしない
        unless is_retry_included?(WARNING_ADEQUATE_QUANTITY)
          #警告(リトライ)
          make_retry_include(WARNING_ADEQUATE_QUANTITY)
          msg = LOWMJ0001+LOWMD0001+retry_array.join(', ')
          raise UserOperationError, msg
        end
      end
    end
  end
  
  #== 締めチェック
  def cutoff_check_target?(params)
    #キホン的に完了以外はチェックしない
    state_code = get_value_by_name(params, 'shipping_state_code')
    if state_code == MCODE_SHIPPING_STATE_COMP
      return true
    end
    return false
  end
  
  
end
