class Licitacao::OrdemDeCompra < ApplicationRecord
	has_paper_trail

	include AASM
	include TradutorConcern
	include IncrementadorDeCodigoConcern

	attr_default :status, :aberto
	attr_default :descrimina_itens_do_empenho, true
	attr_default :observacao, Proc.new { self.try(:empenho).try(:historico) }

	attr_accessor :exercicio
	attr_accessor :unidade_orcamentaria_form
	attr_accessor :itens_da_ordem_de_compra_hidden

	belongs_to :almoxarifado, class_name: 'GestaoDeEstoque::Almoxarifado'
	belongs_to :orcamento, class_name: 'Orcamento'
	belongs_to :empenho, class_name: 'Contabilidade::Empenho'
	belongs_to :ordem_de_compra_primaria, class_name: 'Licitacao::OrdemDeCompra', foreign_key: "ordem_de_compra_id"
	belongs_to :pessoa, class_name: 'Base::Pessoa', foreign_key: "dados_do_faturamento_pessoa_id"

	delegate :unidade_orcamentaria, :elemento_de_despesa, :sub_elemento_de_despesa, to: :empenho, :allow_nil => true

	has_many :ordens_de_compra, class_name: 'Licitacao::OrdemDeCompra'
	has_many :itens_da_ordem_de_compra, dependent: :destroy
	has_many :recebimento_de_materiais, class_name: 'GestaoDeEstoque::RecebimentoDeMaterial'
	has_many :ocorrencias_da_ordem_de_compra
	has_many :itens, through: :itens_da_ordem_de_compra

	accepts_nested_attributes_for :itens_da_ordem_de_compra, reject_if: :all_blank, allow_destroy: true

	before_validation :atribui_numero
	before_destroy :valida_delecao

	validates_presence_of :data_da_solicitacao, :empenho_id, :prazo_de_entrega, :endereco, :setor_solicitante, :observacao
	validates_presence_of :motivo_do_cancelamento, :if => :cancelado?
	validates_presence_of :motivo_da_recusa, :if => :recusado_pelo_almoxarifado?

	validates :data_da_solicitacao, sabado_ou_domingo_ou_feriado: { flexivel: false }

	validate :data_da_ordem_deve_ser_maior_ou_igual_a_data_do_empenho
	validate :valor_menor_ou_igual_ao_empenho, if: Proc.new { self.empenho.present? }
	validate :empenho_esta_confirmado?
	validate :valida_prazo_de_entrega
	validate :empenho_tem_item?
	validate :valida_se_pelo_menos_um_esta_preenchido, if: Proc.new { self.itens_da_ordem_de_compra.any? }

	before_save :lancar_ocorrencia
	before_create :define_tipo_de_ordem_de_compra

	scope :valido, -> { where.not(status: [:cancelado, :recusado_pelo_almoxarifado]) }
	scope :recebido, -> { where(status: [:aberto, :fechado]) }
	scope :recebido_parcial_ou_totalmente, -> { where(status: [:recebido_pelo_almoxarifado, :recebido_parcialmente_pelo_almoxarifado]) }
	scope :retornados, -> { where(status: [:devolvido_ao_fornecedor, :devolvido_pelo_almoxarifado, :recusado_pelo_almoxarifado, :cancelado]) }

	enum status: {
		aberto: 0, # administrativo
		fechado: 1, # administrativo
		enviado_para_almoxarifado: 2, # gestão de estoque
		recebido_pelo_almoxarifado: 3, # gestão de estoque
		recusado_pelo_almoxarifado: 4, # gestão de estoque
		recebido_parcialmente_pelo_almoxarifado: 5, # gestão de estoque
		devolvido_ao_fornecedor: 6, # gestão de estoque
		devolvido_pelo_almoxarifado: 7, # administrativo
		cancelado: 98 # administrativo
	}

	enum tipo_de_ordem_de_compra: {
		ordem_de_fornecimento: 1,
		ordem_de_servico: 2
	}

	aasm column: :status, enum: true, whiny_transitions: false do
		state :aberto, :initial => true
		state :fechado
		state :enviado_para_almoxarifado
		state :recebido_pelo_almoxarifado
		state :recusado_pelo_almoxarifado
		state :recebido_parcialmente_pelo_almoxarifado
		state :devolvido_ao_fornecedor
		state :devolvido_pelo_almoxarifado
		state :cancelado

		event :cancelar do
			transitions to: :cancelado
		end

		event :fechar do
			transitions from: :aberto, to: :fechado do
				guard do
					self.eh_possivel_fechar?
				end
			end
			transitions from: :devolvido_pelo_almoxarifado, to: :fechado do
				guard do
					self.eh_possivel_fechar?
				end
			end
		end

		event :enviar_para_almoxarifado do
			transitions to: :enviado_para_almoxarifado do 
				guard do 
					(fechado? && eh_ordem_de_fornecimento? && utiliza_controle_de_estoque? && almoxarifado.present? && itens_da_ordem_de_compra.size > 0) || ((recebido_pelo_almoxarifado? || recebido_parcialmente_pelo_almoxarifado?) && eh_ordem_de_fornecimento? && utiliza_controle_de_estoque? && almoxarifado.present? && itens_da_ordem_de_compra.size > 0)
				end
			end
		end

		event :confirmar_recebimento_almoxarifado do
			transitions to: :recebido_pelo_almoxarifado
		end

		event :confirmar_recebimento_parcial_almoxarifado do
			transitions to: :recebido_parcialmente_pelo_almoxarifado
		end

		event :recusar do
			transitions to: :recusado_pelo_almoxarifado
		end

		event :retornar_para_aberto do
			transitions to: :aberto do
				guard do
					:fechado? || :enviado_para_almoxarifado?
				end
			end
		end

		event :devolver_ao_fornecedor do
			transitions to: :devolvido_ao_fornecedor
		end

		event :devolvido_pelo_almoxarifado do
			transitions to: :devolvido_pelo_almoxarifado do
				guard do
					self.devolvido_ao_fornecedor? || self.recusado_pelo_almoxarifado?
				end
			end
		end

		event :liberar_saldo_dos_itens do
			transitions to: :recebido_pelo_almoxarifado do
				guard do
					self.recebido_parcialmente_pelo_almoxarifado?
				end

				after do
					self.liberar_saldo_dos_itens
				end
			end
		end
	end

	def tipo_de_label
		if self.status == 'aberto' || self.status == 'recebido_parcialmente_pelo_almoxarifado'
			"info"
		elsif self.status == 'fechado' || self.status == 'recebido_pelo_almoxarifado'
			"success"
		elsif self.status == 'enviado_para_almoxarifado' || self.status == 'devolvido_pelo_almoxarifado'
			"warning"
		elsif self.status == 'devolvido_ao_fornecedor' || self.status == 'cancelado' || self.status == 'recusado_pelo_almoxarifado'
			"danger"
		end
	end

	def possui_itens_recebidos?
		quantidade_recebida = itens_da_ordem_de_compra.sum(&:quantidade_recebida).to_f
		if quantidade_recebida > 0
			return true
		else
			return false
		end
	end

	def possui_itens_a_receber?
		quantidade_a_receber = itens_da_ordem_de_compra.sum(&:quantidade_a_receber).to_f
		if quantidade_a_receber > 0
			return true
		else
			return false
		end
	end

	def possui_recebimento_de_material?
		self.recebimento_de_materiais.any?
	end

	def numero_e_fornecedor
		"#{self.numero} - #{self.empenho.pessoa.nome}" if self.empenho.present?
	end

	# BOOLEANS
	def empenho_esta_confirmado?
		if self.empenho.present? && !self.empenho.confirmado?
			errors.add(:base, "Empenho não está confirmado.")
		end
	end

	def eh_ordem_de_fornecimento?
		empenho.orcamento_da_despesa.elemento_de_despesa.categoria == "30" || empenho.orcamento_da_despesa.elemento_de_despesa.categoria == "32" || empenho.orcamento_da_despesa.elemento_de_despesa.categoria == "52" rescue false
	end

	def marca_presente?
		self.itens_da_ordem_de_compra.map(&:marca).join("").blank?
	end

	# VALORES
	def tipo_de_ordem
		if eh_ordem_de_fornecimento?
			return 'Ordem de Fornecimento'
		else
			return 'Ordem de Serviço'
		end
	end

	def valor_total
		valor_total = itens_da_ordem_de_compra.inject(0) { |total, item| total + item.total.to_f }
		valor_total.to_f.round(2)
	end

	def valor_total_salvo
		itens_da_ordem_de_compra.sum(:total).to_f
	end

	def valor_total_usado_no_empenho
		self.empenho.valor_total_ordens_de_compra
	end

	def valor_geral_do_empenho_menos_esta_ordem
		self.empenho.valor_total_do_empenho.to_f - self.empenho.valor_anulado.to_f - self.empenho.ordens_de_compra.valido.where.not(id: self.id).to_a.sum(&:valor_total).to_f - self.empenho.valor_total_empenhos_vinculados_ao_recebimento
	end

	# VALIDAÇÕES
	def valor_insuficiente_no_empenho?
		self.valor_geral_do_empenho_menos_esta_ordem.to_f.round(2) < self.valor_total.to_f.round(2)
	end

	def valor_menor_ou_igual_ao_empenho
		if valor_total_usado_no_empenho.to_f.round(2) > self.empenho.valor_total_do_empenho.to_f.round(2)
			errors.add(:valor_total, "O valor total da ordem de compra não pode ser maior que o saldo disponível.")
		end
	end

	def data_da_ordem_deve_ser_maior_ou_igual_a_data_do_empenho
		if empenho.present? && self.data_da_solicitacao.present? && (self.data_da_solicitacao < empenho.data_de_solicitacao)
			errors.add(:data_da_solicitacao, "não pode ser anterior à data do empenho: #{self.empenho.data_de_solicitacao}")
		end
	end

	def valida_delecao
		if self.itens_da_ordem_de_compra.any? || self.cancelado?
			raise Exception.new('Não é possivel deletar ordens de fornecimento com itens ou canceladas')
		end
	end

	def saldo_da_ordem_de_compra_primaria
		self.ordem_de_compra_primaria.itens_da_ordem_de_compra.sum(:total)
	end

	def valida_prazo_de_entrega
		if !self.prazo_de_entrega.blank? && self.prazo_de_entrega < self.data_da_solicitacao
			errors.add(:prazo_de_entrega, "O prazo para entrega não pode ser inferior a data de solicitação")
		end
	end

	def possui_saldo_para_requisitar?
		ids_recebimentos = self.recebimento_de_materiais.where( status: [ GestaoDeEstoque::RecebimentoDeMaterial.status[:recebido], GestaoDeEstoque::RecebimentoDeMaterial.status[:recebido_parcialmente] ] ).ids

		if ids_recebimentos.present?
			requisicoes_ids = Administrativo::RequisicaoDeMaterial.where.not(status: :devolvido_ao_almoxarifado).where(recebimento_de_material_id: ids_recebimentos).ids
			total_ja_requisitado = Administrativo::ItemDaRequisicaoDeMaterial.where(requisicao_de_material_id: requisicoes_ids).where.not(valor_total: nil).sum(&:valor_total)
			return total_ja_requisitado < self.valor_total_salvo
		else
			return false
		end
	end

	def possui_saldo_para_receber?
		ids_recebimentos = self.recebimento_de_materiais.where( status: [ GestaoDeEstoque::RecebimentoDeMaterial.status[:recebido], GestaoDeEstoque::RecebimentoDeMaterial.status[:recebido_parcialmente] ] ).ids
		
		if ids_recebimentos.present?
			return GestaoDeEstoque::ItemDoRecebimentoDeMaterial.where(recebimento_de_material_id: ids_recebimentos).select{ |item| item.possui_saldo_disponivel? }.any?
		else
			return true
		end
	end

	def liberar_saldo_dos_itens
		begin
			self.itens_da_ordem_de_compra.each do |item_da_ordem_de_compra|
				item_da_ordem_de_compra.quantidade = item_da_ordem_de_compra.quantidade_recebida
				item_da_ordem_de_compra.total = (item_da_ordem_de_compra.quantidade_recebida * item_da_ordem_de_compra.valor_unitario)
				item_da_ordem_de_compra.save!(validate: false)
			end
		rescue
			false
		end
	end

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

	def data_do_recebimento
		self.recebimento_de_materiais.last.data_do_recebimento
	end

	def quantidade_a_receber
		resto_do_recebimento = itens_da_ordem_de_compra.inject(0) { |total, item| total + item.quantidade_a_receber.to_f }
		resto_do_recebimento.to_f.round(2)
	end

	def empenho_tem_item?
		if self.empenho.present?
			errors.add(:base, "Empenho selecionado não tem itens") if self.eh_ordem_de_fornecimento? && self.empenho.itens_do_empenho.empty?
		end
	end

	def define_tipo_de_ordem_de_compra
		if self.eh_ordem_de_fornecimento?
			self.tipo_de_ordem_de_compra = "ordem_de_fornecimento"
		else
			self.tipo_de_ordem_de_compra = "ordem_de_servico"
		end
	end

	def valida_se_pelo_menos_um_esta_preenchido
		unless self.itens_da_ordem_de_compra.any? { |item| item.total.to_f > 0 }
			errors.add(:base, "É necessário preencher pelo menos um item")
		end
	end

	def eh_possivel_fechar?
		if self.ordem_de_fornecimento?
			Configuracao.last.usa_modulo_gestao_de_estoque? && self.almoxarifado.present? && !self.itens_da_ordem_de_compra.where("marca is null or marca = ''").any?
		else
			( Configuracao.last.usa_modulo_gestao_de_estoque? == false ) || ( self.ordem_de_servico? )
		end
	end

	def utiliza_controle_de_estoque?
		if self.almoxarifado.present?
			@utiliza_controle_de_estoque ||= GestaoDeEstoque::UnidadeOrcamentariaDoAlmoxarifado.find_by(almoxarifado_id: almoxarifado.id ,unidade_orcamentaria_id: unidade_orcamentaria.id).try(:controle_de_estoque?)
		end
	end

	private
	def atribui_numero
		if self.ordem_de_compra_primaria.present? && self.numero.blank?
			qtd_secundaria = self.ordem_de_compra_primaria.ordens_de_compra.size.to_i + 1
			codigo_secundaria = qtd_secundaria.to_s.rjust(3, '0')
			self.numero = self.ordem_de_compra_primaria.numero + '.' + codigo_secundaria
		else
			gerar_codigo(data_da_solicitacao, :numero, :data_da_solicitacao, :orcamento_id, self.orcamento_id) unless self.numero.present? and self.data_da_solicitacao.present?
		end
	end

	def lancar_ocorrencia
		if self.status_changed? && self.status != "aberto"
			Licitacao::OcorrenciaDaOrdemDeCompra.create(
				ordem_de_compra_id: self.id,
				ocorrencia: self.status,
				motivo: self.motivo_da_recusa.present? ? self.motivo_da_recusa : "-"
			).save(validate: false)
		end
	end
end
