class Administrativo::RequisicaoDeMaterial < ApplicationRecord
	has_paper_trail

	include GeradorDeEventosContabeis
	include AASM
	include TradutorConcern
	include IncrementadorDeCodigoConcern

	#gerador_de_eventos_contabeis codigo: 12, atributo_data: 'data_da_requisicao', atributo_codigo_movimentacao: 'numero_da_requisicao'

	attr_accessor :marca_todos_centro_de_custo
	attr_accessor :logado_no_adm

	attr_default :logado_no_adm, false

	belongs_to :transferencia, class_name: "GestaoDeEstoque::Transferencia"
	belongs_to :orcamento, class_name: "Orcamento"
	belongs_to :recebimento_de_material, class_name: "GestaoDeEstoque::RecebimentoDeMaterial"
	belongs_to :unidade_orcamentaria, class_name: "Loa::UnidadeOrcamentaria"
	belongs_to :almoxarifado, class_name: "GestaoDeEstoque::Almoxarifado"
	belongs_to :fornecedor, class_name: "Base::Pessoa", foreign_key: :fornecedor_id
	belongs_to :beneficiado, class_name: "Base::Pessoa", foreign_key: :beneficiado_id
	belongs_to :usuario, class_name: "Usuario"
	belongs_to :almoxarifado_destino , class_name: "GestaoDeEstoque::Almoxarifado", foreign_key: :almoxarifado_destino_id
	belongs_to :agente_publico, class_name: "Base::AgentePublicoMunicipal", foreign_key: :responsavel_id

	delegate :elemento_de_despesa, :sub_elemento_de_despesa, to: :recebimento_de_material, :allow_nil => true

	has_many :itens_das_requisicoes_de_materiais, class_name: "Administrativo::ItemDaRequisicaoDeMaterial", dependent: :destroy
	has_many :itens, through: :itens_das_requisicoes_de_materiais
	has_many :movimentacoes_do_estoque, as: :origem, class_name: "GestaoDeEstoque::MovimentacaoDoEstoque"
	has_many :detalhamentos_da_requisicao_de_material
	has_many :centros_de_custo_da_requisicao, class_name: "Controladoria::CentroDeCustoDaRequisicao"

	has_one :ordem_de_compra, through: :recebimento_de_material, class_name: "Licitacao::OrdemDeCompra"

	accepts_nested_attributes_for :itens_das_requisicoes_de_materiais, allow_destroy: true, reject_if: :all_blank
	accepts_nested_attributes_for :detalhamentos_da_requisicao_de_material, allow_destroy: true
	accepts_nested_attributes_for :centros_de_custo_da_requisicao, allow_destroy: true

	# validações
	validates_associated :itens_das_requisicoes_de_materiais, if: :new_record?
	validates :itens_das_requisicoes_de_materiais, uniq_nested_attributes: { atributo: :item_id, mensagem: "item deve ser único dentro de uma requisição" }
	validates_presence_of :orcamento_id, :status, :almoxarifado_id, :almoxarifado_destino_id, :unidade_orcamentaria_id, :data_da_requisicao, :numero_da_requisicao
	validates_presence_of :responsavel_id, unless: Proc.new { solicitacao_consumo? }
	validate :origem_do_detalhamento_precisa_existir
	validate :nao_pode_ser_o_mesmo_almoxarifado
	validate :detalhamento_precisa_existir, if: Proc.new { self.doacao? || self.devolucao_estorno? || self.ajuste_de_inventario?}
	validate :soma_dos_valores_dos_itens_nao_pode_ser_maior_que_saldo_em_estoque, if: Proc.new { self.utiliza_centro_de_custo? }
	validate :nao_pode_deixar_de_utilizar_centro_de_custos_se_ja_tiver_itens, if: Proc.new { self.utiliza_centro_de_custo_was }
	validates :data_da_requisicao, sabado_ou_domingo_ou_feriado: { flexivel: false }
	# validate :nao_pode_trocar_de_unidade_se_ja_tiver_itens_no_centro_de_custo

	before_validation :atribui_numero_disponivel
	after_create :cria_detalhamento, if: Proc.new { self.tipo_de_requisicao.present? && self.sub_elemento_de_despesa.present? && self.detalhamentos_da_requisicao_de_material.empty? }
	after_save :deleta_centro_de_custos_que_nao_estejam_marcado_para_cadastro, if: Proc.new { self.utiliza_centro_de_custo? }
	after_save :deleta_centro_de_custos_quando_muda_a_unidade_gestora, if: Proc.new { self.utiliza_centro_de_custo? }
	after_save :deleta_centro_de_custos_quando_deixa_de_utilizar, unless: Proc.new { self.centros_de_custos_ja_possuem_itens? }

	enum status: {
		aberto: 0,
		fechado: 1,
		enviado_ao_almoxarifado: 2,
		recebido_pelo_almoxarifado: 3,
		recusado: 4,
		em_atendimento: 5,
		atendido_parcialmente: 6,
		atendido: 7,
		devolvido_ao_almoxarifado: 8,
		confirmado: 9,
		devolvido_parcialmente_ao_almoxarifado: 10,
		consumido: 11,
		retornado_ao_administrativo: 12
	}

	enum tipo_de_requisicao: {
		comum: 0,
		demanda_programada: 1
	}

	enum classificacao: {
		doacao: 0,
		devolucao_estorno: 1,
		ajuste_de_inventario: 2,
		transferencia: 3 ,
		remoto: 4
	}

	enum tipo_de_material: {
		consumo: 0,
		permanente: 1,
		consumo_distribuicao_gratuita: 2
	}

	enum classificacao_tipo_de_material: {
		material_de_expediente: 0,
		material_de_consumo: 1,
		generos_alimenticios: 2,
		materiais_de_construcao: 3,
		autopecas: 4,
		medicamentos_e_materiais_hospitalares: 5,
		materiais_graficos: 6
	}

	# retirar
	enum tipo_de_solicitacao: {
		solicitacao_requisicao: 0,
		solicitacao_consumo: 1
	}

	# retirar e mudar para consumo
	enum tipo_de_consumo: {
		interno: 0,
		saida_de_material: 1
	}

	aasm column: :status, enum: true, whiny_transitions: false do
		state :aberto, :initial => true
		state :fechado
		state :enviado_ao_almoxarifado
		state :recebido_pelo_almoxarifado
		state :recusado
		state :em_atendimento
		state :atendido_parcialmente
		state :atendido
		state :devolvido_ao_almoxarifado
		state :confirmado
		state :devolvido_parcialmente_ao_almoxarifado
		state :consumido
		state :retornado_ao_administrativo

		event :reabrir_requisicao do
			transitions from: :fechado, to: :aberto
		end

		event :fechar_requisicao do
			transitions from: :aberto, to: :fechado do
				guard do
					if eh_demanda_programada?
						self.itens_das_requisicoes_de_materiais.any? && possui_demanda_programada?
					else
						self.itens_das_requisicoes_de_materiais.any? && self.solicitacao_consumo? == false
					end
				end
			end
		end

		event :concluir_consumo do
			transitions from: :aberto, to: :consumido do
				guard do
					self.itens_das_requisicoes_de_materiais.any? && self.solicitacao_consumo?
				end
				after do
					self.itens_das_requisicoes_de_materiais.each do |item_da_requisicao|
						item_da_requisicao.update_attribute(:quantidade_atendida, item_da_requisicao.quantidade_requisitada)
						item_da_requisicao.adiciona_ou_atualiza_saldo_no_estoque
					end
				end
			end
		end

		event :enviar_ao_almoxarifado do
			transitions from: :fechado, to: :enviado_ao_almoxarifado do
				guard do
					solicitacao_consumo? == false
				end
			end

			transitions from: :retornado_ao_administrativo, to: :enviado_ao_almoxarifado do 
				guard do
					solicitacao_consumo? == false
				end
			end
		end

		event :receber_no_almoxarifado do
			transitions from: :enviado_ao_almoxarifado, to: :recebido_pelo_almoxarifado do
				guard do
					itens_com_saldo_em_estoque?
				end
			end
		end

		event :recusar do
			transitions from: :enviado_ao_almoxarifado, to: :recusado
		end

		event :atender_requisicao do
			transitions from: [:atendido_parcialmente, :recebido_pelo_almoxarifado], to: :em_atendimento do
				guard do
					existe_item_atendido? && eh_demanda_programada? && possui_demanda_programada_para_hoje?
				end
			end
			transitions from: :recebido_pelo_almoxarifado, to: :em_atendimento do
				guard do
					itens_com_saldo_em_estoque?
				end
			end
		end

		event :concluir_atendimento_parcial do
			transitions from: :em_atendimento, to: :atendido_parcialmente do
				guard do
					existe_item_atendido? && self.itens_com_saldo_menor_que_o_requisitado? == false
				end
			end
			after {
				self.itens_das_requisicoes_de_materiais.each do |item_da_requisicao_de_material|
					item_da_requisicao_de_material.adiciona_ou_atualiza_saldo_no_estoque
				end
			}
		end

		event :concluir_atendimento do
			transitions from: [:aberto, :em_atendimento], to: :atendido do
				guard do
					existe_item_atendido? && self.itens_com_saldo_menor_que_o_requisitado? == false
				end
			end
			after {
				self.itens_das_requisicoes_de_materiais.each do |item_da_requisicao_de_material|
					item_da_requisicao_de_material.adiciona_ou_atualiza_saldo_no_estoque
				end
			}
		end

		event :retornar_ao_administrativo do
			transitions from: :recusado, to: :retornado_ao_administrativo
		end
	end

	def esta_atendido?
		self.atendido_parcialmente? || self.atendido?
	end

	def possui_demanda_programada?
		self.itens_das_requisicoes_de_materiais.select { |demanda| demanda.possui_demanda_programada? }.present?
	end

	def possui_demanda_programada_para_hoje?
		self.itens_das_requisicoes_de_materiais.select { |demanda| demanda.possui_demanda_programada_para_hoje? }.present?
	end

	def eh_demanda_programada?
		self.demanda_programada?
	end

	def eh_avulsa?
		self.avulsa?
	end

	def possui_classificacao?
		self.classificacao.present? && self.remoto? == false
	end

	def possui_itens?
		self.itens_das_requisicoes_de_materiais.size > 0
	end

	def possui_devolucao?
		GestaoDeEstoque::DevolucaoDeMaterial.where(origem: self.id).any?
	end

	def devolucoes
		GestaoDeEstoque::DevolucaoDeMaterial.where(origem: self.id)
	end

	def tipo_da_devolucao
    GestaoDeEstoque::DevolucaoDeMaterial.where(origem: self.id).last.tipo_de_devolucao rescue false
  end

	def possui_recebimento_de_material?
		self.recebimento_de_material.present?
	end

	def possui_numero_da_requisicao?
		self.numero_da_requisicao.present?
	end

	def atribui_numero_disponivel
		if self.data_da_requisicao.present? && self.numero_da_requisicao.blank?
			gerar_codigo(self.data_da_requisicao, :numero_da_requisicao, :data_da_requisicao, :orcamento_id, self.orcamento_id)
		end
	end

	def atendimento_parcial?
		self.itens_das_requisicoes_de_materiais.where("quantidade_requisitada <> quantidade_atendida").present?
	end

	def existe_item_atendido?
		self.itens_das_requisicoes_de_materiais.where("quantidade_atendida > 0").present?
	end

	def numero_e_quantidade_itens
		"#{self.numero_da_requisicao} (#{self.itens_das_requisicoes_de_materiais.size.to_i} Itens)" if self.numero_da_requisicao.present?
	end

	def saldo_disponivel_para_devolucao
		self.itens_das_requisicoes_de_materiais.sum(&:quantidade_disponivel_para_devolucao)
	end

	def valor_total_dos_itens_da_requisicoes
		if self.atendido?
			self.itens_das_requisicoes_de_materiais.sum(:valor_total) rescue 0.0
		else
			self.itens_das_requisicoes_de_materiais.sum(:valor_unitario).to_f * self.itens_das_requisicoes_de_materiais.sum(:quantidade_requisitada).to_f rescue 0.0
		end
	end

	def preenche_campos_requisicao_de_material ordem_de_compra
		begin
		if ordem_de_compra.present? && !ordem_de_compra.nil?
			recebimento_de_material = GestaoDeEstoque::RecebimentoDeMaterial.find_by(ordem_de_compra_id: ordem_de_compra)
			self.data_da_requisicao = Date.today
			self.recebimento_de_material = recebimento_de_material
			self.tipo_de_material = recebimento_de_material.tipo_de_material
			self.unidade_orcamentaria = recebimento_de_material.ordem_de_compra.unidade_orcamentaria
			self.almoxarifado = recebimento_de_material.ordem_de_compra.almoxarifado
			self.trazer_itens_do_recebimento = true
		end
		rescue => e
			errors.add(:base, e.message)
			raise ActiveRecord::Rollback, "Não foi possivel preencher os campos."
		end
	end

	def descricao_itens
 		self.itens_das_requisicoes_de_materiais.map { |i| i.item.descricao }.join(", ")
	end

	def cria_recebimento
		recebimento = GestaoDeEstoque::RecebimentoDeMaterial.new(
			data_do_recebimento: Date.today,
			unidade_orcamentaria_id: self.unidade_orcamentaria_destino_id,
			almoxarifado_id: self.almoxarifado_destino_id,
			tipo_de_material: self.tipo_de_material,
			status: 4,
			orcamento_id: self.orcamento_id,
			classificacao_tipo_de_material: GestaoDeEstoque::RecebimentoDeMaterial.classificacoes_tipo_de_material[:material_de_consumo],
			tipo_de_entrada: GestaoDeEstoque::RecebimentoDeMaterial.tipos_de_entradas[:definitivo],
			avulso: false,
			classificacao: GestaoDeEstoque::RecebimentoDeMaterial.classificacoes[:requisicao],
			sub_elemento_de_despesa_id: self.sub_elemento_de_despesa_id
    )
		begin
			if recebimento.save
				self.itens_das_requisicoes_de_materiais.each do |item_da_requisicao|
					item_do_recebimento = GestaoDeEstoque::ItemDoRecebimentoDeMaterial.create!(
						recebimento_de_material_id: recebimento.id,
						item_id: item_da_requisicao.item_id,
						quantidade: item_da_requisicao.quantidade_atendida,
						unidade_de_medida_de_conversao_id: item_da_requisicao.unidade_de_medida_id,
						valor_unitario: item_da_requisicao.valor_unitario,
						total: item_da_requisicao.valor_total,
					)
					item_do_recebimento.adiciona_ou_atualiza_saldo_no_estoque
				end
			end

		rescue => e
			errors.add(:base, e.message)
			raise ActiveRecord::Rollback, "Não foi possivel Salvar o Recebimento. #{e.message}"
		end
  end

	def atender_todos_os_itens
		self.itens_das_requisicoes_de_materiais.each do |item_da_requisicao|
			item_da_requisicao.quantidade_atendida = item_da_requisicao.quantidade_requisitada
			item_da_requisicao.save
		end
	end

	def remove_movimentacao_do_estoque
		movimentacao_do_estoque = GestaoDeEstoque::MovimentacaoDoEstoque.find_by(origem_id: self.id, origem_type: self.class.name)
		movimentacao_do_estoque.destroy
	end

	def valor_total_da_requisicao
		self.itens_das_requisicoes_de_materiais.sum(&:valor_total)
	end

	def origem_do_detalhamento_precisa_existir
		if self.detalhamentos_da_requisicao_de_material.empty? && self.trazer_itens_do_recebimento == false
			errors.add(:base_detalhamento, "Precisa existir pelo menos um detalhamento")
		elsif self.trazer_itens_do_recebimento == true && self.recebimento_de_material.blank?
			errors.add(:recebimento_de_material_id, "Não pode ficar em branco")
		end
	end

	def detalhamento_precisa_existir
		if self.detalhamentos_da_requisicao_de_material.empty?
			errors.add(:base_detalhamento, "Precisa existir pelo menos um detalhamento")
		end
	end

	def cria_detalhamento
		if sub_elemento_de_despesa.elemento_de_despesa.codigo == "33903000"
			tipo = "consumo"
		elsif sub_elemento_de_despesa.elemento_de_despesa.codigo == "44905200"
			tipo = "permanente"
		else
			tipo = "consumo_distribuicao_gratuita"
		end
		self.detalhamentos_da_requisicao_de_material.create(sub_elemento_de_despesa_id: sub_elemento_de_despesa.id, tipo_de_material: tipo)
	end

	def itens_com_saldo_menor_que_o_requisitado?
		self.itens_das_requisicoes_de_materiais.any? { |item| item.estoque.quantidade_total_saldo < item.quantidade_requisitada.to_f }
	end

	def itens_com_saldo_em_estoque?
		self.itens_das_requisicoes_de_materiais.select{ |item| item.estoque.quantidade_total_saldo >= item.quantidade_requisitada.to_f }.any?
	end

	def deleta_centro_de_custos_que_nao_estejam_marcado_para_cadastro
		self.centros_de_custo_da_requisicao.each do |centros_de_custo_da_requisicao|
			centros_de_custo_da_requisicao.destroy if centros_de_custo_da_requisicao.marcado_para_cadastro == "0"
		end
	end

	def deleta_centro_de_custos_quando_muda_a_unidade_gestora
		centros_antigos = self.centros_de_custo_da_requisicao.select { |centro_de_custo_da_requisicao| centro_de_custo_da_requisicao.centro_de_custo.unidade_gestora_id != self.centros_de_custo_da_requisicao.last.centro_de_custo.unidade_gestora_id }
		centros_antigos.each do |centro_de_custo_da_requisicao|
			centro_de_custo_da_requisicao.destroy
		end
	end

	def soma_dos_valores_dos_itens_nao_pode_ser_maior_que_saldo_em_estoque
		self.itens_das_requisicoes_de_materiais.group_by{ |item_da_requisicao|  item_da_requisicao.estoque_id }.each do |criterio_de_agrupamento, itens_das_requisicoes_de_materiais|
			saldo_disponivel_no_estoque = itens_das_requisicoes_de_materiais.last.estoque.quantidade_total_saldo
			if itens_das_requisicoes_de_materiais.sum(&:quantidade_requisitada) > saldo_disponivel_no_estoque
				errors.add(:base, "Soma das quantidades do item #{itens_das_requisicoes_de_materiais.last.estoque.item.codigo_e_descricao} não pode ser maior que  saldo disponível no estoque #{saldo_disponivel_no_estoque}")
			end
		end
	end

	def nao_pode_ser_o_mesmo_almoxarifado
		if self.almoxarifado == self.almoxarifado_destino
			errors.add(:base, "Não pode utilizar o mesmo almoxarifado para Origem e Destino.")
		end
	end

	def nao_pode_deixar_de_utilizar_centro_de_custos_se_ja_tiver_itens
		if self.centros_de_custos_ja_possuem_itens? && self.utiliza_centro_de_custo? == false
			errors.add(:utiliza_centro_de_custo, "Não pode deixar de utilizar pois já possui itens com centro de custo")
		end
	end

	def nao_pode_trocar_de_unidade_se_ja_tiver_itens_no_centro_de_custo
		if self.centros_de_custos_ja_possuem_itens? && self.unidade_orcamentaria_destino_id_changed?
			errors.add(:unidade_orcamentaria_destino_id, "Não pode trocar pois já possui itens no centro de custo")
		end
	end

	def centros_de_custos_ja_possuem_itens?
		return self.itens_das_requisicoes_de_materiais.any?{ |item_da_requisicao| self.centros_de_custo_da_requisicao.ids.include?(item_da_requisicao.centro_de_custo_da_requisicao_id) }
	end

	def deleta_centro_de_custos_quando_deixa_de_utilizar
		self.centros_de_custo_da_requisicao.destroy_all if self.centros_de_custo_da_requisicao.any? && self.utiliza_centro_de_custo? == false
	end
end
