from django.core.validators import FileExtensionValidator, MinLengthValidator
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from src.pre_recebimento.base.models import UnidadeMedida
from ...dados_comuns.behaviors import (
CriadoPor,
Logs,
ModeloBase,
TemChaveExterna,
TemIdentificadorExternoAmigavel,
)
from ...dados_comuns.fluxo_status import (
FluxoFichaTecnicaDoProduto,
)
from ...dados_comuns.models import LogSolicitacoesUsuario
from ...dados_comuns.validators import validate_file_size_10mb
from ...produto.models import (
Fabricante,
InformacaoNutricional,
Marca,
NomeDeProdutoEdital,
)
from ...terceirizada.models import Terceirizada
[docs]
class FabricanteFichaTecnica(ModeloBase):
"""Modelo para armazenar informações do fabricante específicas para uma ficha técnica."""
fabricante = models.ForeignKey(
Fabricante,
on_delete=models.SET_NULL,
related_name="fichas_tecnicas_detalhes",
blank=True,
null=True,
)
cnpj = models.CharField(
"CNPJ",
validators=[MinLengthValidator(14)],
max_length=14,
blank=True,
)
cep = models.CharField("CEP", max_length=8, blank=True)
endereco = models.CharField("Endereço", max_length=160, blank=True)
numero = models.CharField("Número", max_length=10, blank=True)
complemento = models.CharField("Complemento", max_length=250, blank=True)
bairro = models.CharField("Bairro", max_length=150, blank=True)
cidade = models.CharField("Cidade", max_length=150, blank=True)
estado = models.CharField("Estado", max_length=150, blank=True)
email = models.EmailField("E-mail", blank=True)
telefone = models.CharField(
"Telefone", max_length=13, validators=[MinLengthValidator(8)], blank=True
)
def __str__(self):
return f"Ficha Técnica - {self.fabricante.nome if self.fabricante else 'Fabricante'}"
class Meta:
verbose_name = "Fabricante da Ficha Técnica"
verbose_name_plural = "Fabricantes das Fichas Técnicas"
[docs]
class FichaTecnicaDoProduto(
ModeloBase, TemIdentificadorExternoAmigavel, Logs, FluxoFichaTecnicaDoProduto
):
CATEGORIA_PERECIVEIS = "PERECIVEIS"
CATEGORIA_NAO_PERECIVEIS = "NAO_PERECIVEIS"
CATEGORIA_FLV = "FLV"
CATEGORIA_CHOICES = (
(CATEGORIA_PERECIVEIS, "Perecíveis"),
(CATEGORIA_NAO_PERECIVEIS, "Não Perecíveis"),
(CATEGORIA_FLV, "FLV"),
)
ARMAZEM = "ARMAZEM"
PONTO_A_PONTO = "PONTO_A_PONTO"
TIPO_ENTREGA_CHOICES = (
(ARMAZEM, "Armazém"),
(PONTO_A_PONTO, "Ponto a Ponto"),
)
MECANISMO_CERTIFICACAO = "CERTIFICACAO"
MECANISMO_OPAC = "OPAC"
MECANISMO_OCS = "OCS"
MECANISMO_CONTROLE_CHOICES = (
(MECANISMO_CERTIFICACAO, "Certificação"),
(MECANISMO_OPAC, "OPAC"),
(MECANISMO_OCS, "OCS"),
)
LEVE_LEITE = "LEVE_LEITE"
ALIMENTACAO_ESCOLAR = "ALIMENTACAO_ESCOLAR"
PROGRAMA_CHOICES = (
(LEVE_LEITE, "Leve Leite"),
(ALIMENTACAO_ESCOLAR, "Alimentação Escolar"),
)
numero = models.CharField(
"Número da Ficha Técnica", blank=True, max_length=250, unique=True
)
produto = models.ForeignKey(
NomeDeProdutoEdital,
on_delete=models.PROTECT,
related_name="fichas_tecnicas",
)
marca = models.ForeignKey(Marca, on_delete=models.PROTECT, blank=True, null=True)
categoria = models.CharField(choices=CATEGORIA_CHOICES, max_length=14, blank=True)
tipo_entrega = models.CharField(
"Tipo de Entrega",
max_length=20,
choices=TIPO_ENTREGA_CHOICES,
default=ARMAZEM,
)
programa = models.CharField(
"Programa",
max_length=20,
choices=PROGRAMA_CHOICES,
default=ALIMENTACAO_ESCOLAR,
)
pregao_chamada_publica = models.CharField(
"Nº do Pregão Eletrônico", max_length=100, blank=True
)
empresa = models.ForeignKey(
Terceirizada,
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="fichas_tecnicas",
)
fabricante = models.ForeignKey(
FabricanteFichaTecnica,
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="fichas_tecnicas_como_fabricante",
)
envasador_distribuidor = models.ForeignKey(
FabricanteFichaTecnica,
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="fichas_tecnicas_como_envasador_distribuidor",
verbose_name="Envasador/Distribuidor",
)
prazo_validade = models.CharField("Prazo de Validade", max_length=150, blank=True)
numero_registro = models.CharField(
"Nº do Registro do Rótulo", max_length=150, blank=True
)
organico = models.BooleanField("É orgânico?", null=True)
mecanismo_controle = models.CharField(
choices=MECANISMO_CONTROLE_CHOICES, max_length=12, blank=True
)
especie_variedade = models.CharField(
"Espécie ou Variedade Cultivada", max_length=150, blank=True
)
componentes_produto = models.TextField("Componentes do Produto", blank=True)
alergenicos = models.BooleanField("Pode conter alergênicos?", null=True)
ingredientes_alergenicos = models.CharField(
"Ingredientes/aditivos alergênicos", max_length=150, blank=True
)
gluten = models.BooleanField("Contém glúten?", null=True)
lactose = models.BooleanField("Contém lactose?", null=True)
lactose_detalhe = models.CharField("Detalhar Lactose", max_length=150, blank=True)
porcao = models.CharField("Porção", max_length=100, blank=True, null=True)
unidade_medida_porcao = models.ForeignKey(
UnidadeMedida,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="fichas_tecnicas_unidade_porcao",
)
valor_unidade_caseira = models.CharField(
"Unidade Caseira",
max_length=100,
blank=True,
null=True,
)
unidade_medida_caseira = models.CharField(
"Unidade de Medida Caseira",
max_length=100,
blank=True,
)
prazo_validade_descongelamento = models.CharField(
"Prazo de Validade Descongelamento",
help_text="Prazo de Validade após o descongelamento e mantido sob refrigeração",
max_length=250,
blank=True,
)
condicoes_de_conservacao = models.TextField(
"Condições de conservação",
help_text="Condições de conservação e Prazo máximo para consumo após a abertura da embalagem primária",
blank=True,
)
temperatura_congelamento = models.FloatField(
"Temperatura de Congelamento do Produto",
blank=True,
null=True,
)
temperatura_veiculo = models.FloatField(
"Temperatura Interna do Veículo para Transporte",
blank=True,
null=True,
)
condicoes_de_transporte = models.TextField("Condições de Transporte", blank=True)
embalagem_primaria = models.TextField("Embalagem Primária", blank=True)
embalagem_secundaria = models.TextField("Embalagem Secundária", blank=True)
embalagens_de_acordo_com_anexo = models.BooleanField(
"Embalagens de Acordo com Anexo?",
help_text="Declaro que as embalagens primária e secundária em que serão entregues o produto estarão de acordo "
"com as especificações do Anexo I do Edital",
null=True,
)
material_embalagem_primaria = models.TextField(
"Material da Embalagem Primária", blank=True
)
produto_eh_liquido = models.BooleanField("O produto é líquido?", null=True)
volume_embalagem_primaria = models.FloatField(
blank=True, null=True, help_text="Volume do Produto na Embalagem Primária"
)
unidade_medida_volume_primaria = models.ForeignKey(
UnidadeMedida,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="fichas_tecnicas_volume_primaria",
)
peso_liquido_embalagem_primaria = models.FloatField(
blank=True, null=True, help_text="Peso Líquido do Produto na Embalagem Primária"
)
unidade_medida_primaria = models.ForeignKey(
UnidadeMedida,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="fichas_tecnicas_unidade_primaria",
)
peso_liquido_embalagem_secundaria = models.FloatField(
blank=True,
null=True,
help_text="Peso Líquido do Produto na Embalagem Secundária",
)
unidade_medida_secundaria = models.ForeignKey(
UnidadeMedida,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="fichas_tecnicas_unidade_secundaria",
)
peso_embalagem_primaria_vazia = models.FloatField(blank=True, null=True)
unidade_medida_primaria_vazia = models.ForeignKey(
UnidadeMedida,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="fichas_tecnicas_primaria_vazia",
)
peso_embalagem_secundaria_vazia = models.FloatField(blank=True, null=True)
unidade_medida_secundaria_vazia = models.ForeignKey(
UnidadeMedida,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="fichas_tecnicas_secundaria_vazia",
)
variacao_percentual = models.FloatField(blank=True, null=True)
sistema_vedacao_embalagem_secundaria = models.TextField(
"Sistema de Vedação da Embalagem Secundária", blank=True
)
rotulo_legivel = models.BooleanField(
"Rotulo Legível?",
help_text="Declaro que no rótulo da embalagem primária e, se for o caso, da secundária, constarão, de forma "
"legível e indelével, todas as informações solicitadas do Anexo I do Edital",
null=True,
)
nome_responsavel_tecnico = models.CharField(
"Nome completo do Responsável Técnico", max_length=250, blank=True
)
habilitacao = models.CharField("Habilitação", max_length=250, blank=True)
numero_registro_orgao = models.CharField(
"Nº do Registro em Órgão Competente", max_length=250, blank=True
)
arquivo = models.FileField(
upload_to="fichas_tecnicas",
validators=[
FileExtensionValidator(allowed_extensions=["PDF"]),
validate_file_size_10mb,
],
null=True,
)
modo_de_preparo = models.TextField("Modo de Preparo do Produto", blank=True)
informacoes_adicionais = models.TextField("Informações Adicionais", blank=True)
def __str__(self):
return f"{self.numero} - {self.produto.nome}" if self.produto else self.numero
class Meta:
verbose_name = "Ficha Técnica do Produto"
verbose_name_plural = "Fichas Técnicas dos Produtos"
[docs]
def salvar_log_transicao(self, status_evento, usuario, **kwargs):
justificativa = kwargs.get("justificativa", "")
log_transicao = LogSolicitacoesUsuario.objects.create(
descricao=str(self),
status_evento=status_evento,
solicitacao_tipo=LogSolicitacoesUsuario.FICHA_TECNICA_DO_PRODUTO,
usuario=usuario,
uuid_original=self.uuid,
justificativa=justificativa,
)
return log_transicao
def gerar_numero_ficha_tecnica(self):
self.numero = f"FT{str(self.pk).zfill(3)}"
@property
def ponto_a_ponto(self) -> bool:
return self.tipo_entrega == self.PONTO_A_PONTO
[docs]
@receiver(post_save, sender=FichaTecnicaDoProduto)
def gerar_numero_ficha_tecnica(sender, instance, created, **kwargs):
if created:
instance.gerar_numero_ficha_tecnica()
instance.save()
[docs]
class AnaliseFichaTecnica(ModeloBase, CriadoPor):
ficha_tecnica = models.ForeignKey(
FichaTecnicaDoProduto,
on_delete=models.CASCADE,
related_name="analises",
)
fabricante_envasador_conferido = models.BooleanField(null=True)
fabricante_envasador_correcoes = models.TextField(blank=True)
detalhes_produto_conferido = models.BooleanField(null=True)
detalhes_produto_correcoes = models.TextField(blank=True)
informacoes_nutricionais_conferido = models.BooleanField(null=True)
informacoes_nutricionais_correcoes = models.TextField(blank=True)
conservacao_conferido = models.BooleanField(null=True)
conservacao_correcoes = models.TextField(blank=True)
temperatura_e_transporte_conferido = models.BooleanField(null=True)
temperatura_e_transporte_correcoes = models.TextField(blank=True)
armazenamento_conferido = models.BooleanField(null=True)
armazenamento_correcoes = models.TextField(blank=True)
embalagem_e_rotulagem_conferido = models.BooleanField(null=True)
embalagem_e_rotulagem_correcoes = models.TextField(blank=True)
responsavel_tecnico_conferido = models.BooleanField(null=True)
responsavel_tecnico_correcoes = models.TextField(blank=True)
modo_preparo_conferido = models.BooleanField(null=True)
modo_preparo_correcoes = models.TextField(blank=True)
outras_informacoes_conferido = models.BooleanField(null=True)
@property
def aprovada(self):
valido = (
(
self.fabricante_envasador_conferido is True
and not self.fabricante_envasador_correcoes
)
and (
self.detalhes_produto_conferido is True
and not self.detalhes_produto_correcoes
)
and (
self.temperatura_e_transporte_conferido in [True, None]
and not self.temperatura_e_transporte_correcoes
)
and (
self.responsavel_tecnico_conferido is True
and not self.responsavel_tecnico_correcoes
)
and self.outras_informacoes_conferido is True
)
if not valido:
return False
if not (
self.ficha_tecnica.categoria == "FLV"
and self.ficha_tecnica.tipo_entrega == "PONTO_A_PONTO"
):
valido_extra = (
(
self.informacoes_nutricionais_conferido is True
and not self.informacoes_nutricionais_correcoes
)
and (
self.conservacao_conferido is True
and not self.conservacao_correcoes
)
and (
self.armazenamento_conferido is True
and not self.armazenamento_correcoes
)
and (
self.embalagem_e_rotulagem_conferido is True
and not self.embalagem_e_rotulagem_correcoes
)
and (
self.modo_preparo_conferido is True
and not self.modo_preparo_correcoes
)
)
return valido_extra
return True
class Meta:
verbose_name = "Análise da Ficha Técnica"
verbose_name_plural = "Análises das Fichas Técnicas"