import datetime
import os
from django.core.validators import FileExtensionValidator
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.template.loader import render_to_string
from rest_framework.exceptions import ValidationError
from src.dados_comuns.utils import convert_image_to_base64
from src.pre_recebimento.base.models import UnidadeMedida
from src.pre_recebimento.cronograma_entrega.models import Cronograma
from src.relatorios.utils import merge_pdf_com_string_template
from ...dados_comuns.behaviors import (
Logs,
ModeloBase,
TemChaveExterna,
TemIdentificadorExternoAmigavel,
)
from ...dados_comuns.fluxo_status import (
FluxoDocumentoDeRecebimento,
)
from ...dados_comuns.models import LogSolicitacoesUsuario
from ...dados_comuns.validators import validate_file_size_10mb
[docs]
class ArquivoDoTipoDeDocumento(TemChaveExterna):
tipo_de_documento = models.ForeignKey(
"TipoDeDocumentoDeRecebimento",
on_delete=models.CASCADE,
related_name="arquivos",
blank=True,
)
arquivo = models.FileField(
upload_to="documentos_de_recebimento",
validators=[
FileExtensionValidator(allowed_extensions=["PDF", "PNG", "JPG", "JPEG"]),
validate_file_size_10mb,
],
)
nome = models.CharField(max_length=100, blank=True)
def __str__(self):
return (
f"{self.tipo_de_documento.tipo_documento} - {self.nome}"
if self.tipo_de_documento
else str(self.id)
)
def delete(self, *args, **kwargs):
# Antes de excluir o objeto, exclui o arquivo associado
if self.arquivo:
if os.path.isfile(self.arquivo.path):
os.remove(self.arquivo.path)
super().delete(*args, **kwargs)
class Meta:
verbose_name = "Arquivo do Tipo de Documento"
verbose_name_plural = "Arquivos dos Tipos de Documentos"
[docs]
class TipoDeDocumentoDeRecebimento(TemChaveExterna):
TIPO_DOC_LAUDO = "LAUDO"
TIPO_DOC_DECLARACAO_LEI_1512010 = "DECLARACAO_LEI_1512010"
TIPO_DOC_CERTIFICADO_CONF_ORGANICA = "CERTIFICADO_CONF_ORGANICA"
TIPO_DOC_RASTREABILIDADE = "RASTREABILIDADE"
TIPO_DOC_DECLARACAO_MATERIA_ORGANICA = "DECLARACAO_MATERIA_ORGANICA"
TIPO_DOC_OUTROS = "OUTROS"
TIPO_DOC_CHOICES = (
(TIPO_DOC_LAUDO, "Laudo"),
(
TIPO_DOC_DECLARACAO_LEI_1512010,
"Declaração de atendimento a Lei Municipal: 15.120/10",
),
(TIPO_DOC_CERTIFICADO_CONF_ORGANICA, "Certificado de conformidade orgânica"),
(TIPO_DOC_RASTREABILIDADE, "Rastreabilidade"),
(TIPO_DOC_DECLARACAO_MATERIA_ORGANICA, "Declaração de Matéria Láctea"),
(TIPO_DOC_OUTROS, "Outros"),
)
documento_recebimento = models.ForeignKey(
"DocumentoDeRecebimento",
on_delete=models.CASCADE,
blank=True,
related_name="tipos_de_documentos",
)
tipo_documento = models.CharField(
choices=TIPO_DOC_CHOICES, max_length=35, blank=True
)
descricao_documento = models.TextField("Descrição do Documento", blank=True)
def __str__(self):
if self.documento_recebimento:
return f"{self.documento_recebimento.cronograma.numero} - {self.tipo_documento}"
else:
return str(self.id)
class Meta:
verbose_name = "Tipo de Documento de Recebimento"
verbose_name_plural = "Tipos de Documentos de Recebimento"
unique_together = ["documento_recebimento", "tipo_documento"]
[docs]
class DocumentoDeRecebimento(
ModeloBase, TemIdentificadorExternoAmigavel, Logs, FluxoDocumentoDeRecebimento
):
cronograma = models.ForeignKey(
Cronograma,
on_delete=models.PROTECT,
related_name="documentos_de_recebimento",
)
numero_laudo = models.CharField("Número do Laudo", blank=True, max_length=50)
laboratorio = models.ForeignKey(
"Laboratorio",
on_delete=models.PROTECT,
blank=True,
null=True,
default=None,
related_name="documentos_de_recebimento",
)
quantidade_laudo = models.DecimalField(
max_digits=15,
decimal_places=2,
blank=True,
null=True,
help_text="Quantidade do laudo com 2 casas decimais",
)
numero_lote_laudo = models.CharField("Número do Laudo", blank=True, max_length=200)
unidade_medida = models.ForeignKey(
UnidadeMedida, on_delete=models.PROTECT, blank=True, null=True, default=None
)
data_final_lote = models.DateField("Data Final do Lote", blank=True, null=True)
correcao_solicitada = models.TextField("Correção Solicitada", blank=True)
[docs]
def salvar_log_transicao(self, status_evento, usuario, **kwargs):
justificativa = kwargs.get("justificativa", "")
LogSolicitacoesUsuario.objects.create(
descricao=str(self),
status_evento=status_evento,
solicitacao_tipo=LogSolicitacoesUsuario.DOCUMENTO_DE_RECEBIMENTO,
usuario=usuario,
uuid_original=self.uuid,
justificativa=justificativa,
)
@property
def arquivo_laudo_assinado(self):
log_aprovacao = self.logs.filter(
status_evento=LogSolicitacoesUsuario.DOCUMENTO_APROVADO
).first()
if not log_aprovacao:
raise ValidationError(
"Não foi possível encontrar o log de Aprovação do Documento de Recebimento para gerar o Laudo assinado."
)
laudo = self.tipos_de_documentos.filter(
tipo_documento=TipoDeDocumentoDeRecebimento.TIPO_DOC_LAUDO
).first()
arquivo_laudo = laudo.arquivos.first()
logo_sipae = convert_image_to_base64(
"src/relatorios/static/images/logo-sigpae.png", "png"
)
string_template = render_to_string(
"pre_recebimento/documentos_recebimento/rodape_assinatura_digital_laudo.html",
{
"logo_sigpae": logo_sipae,
"usuario": log_aprovacao.usuario,
"data_hora": log_aprovacao.criado_em,
},
)
return merge_pdf_com_string_template(
arquivo_pdf=arquivo_laudo.arquivo,
string_template=string_template,
somente_ultima_pagina=True,
)
def __str__(self):
return (
f"{self.cronograma.numero} - Laudo: {self.numero_laudo}"
if self.cronograma
else str(self.numero_laudo)
)
class Meta:
verbose_name = "Documento de Recebimento"
verbose_name_plural = "Documentos de Recebimento"
[docs]
class DataDeFabricaoEPrazo(TemChaveExterna):
PRAZO_30 = "30"
PRAZO_60 = "60"
PRAZO_90 = "90"
PRAZO_120 = "120"
PRAZO_180 = "180"
PRAZO_OUTRO = "OUTRO"
PRAZO_CHOICES = (
(PRAZO_30, "30 dias"),
(PRAZO_60, "60 dias"),
(PRAZO_90, "90 dias"),
(PRAZO_120, "120 dias"),
(PRAZO_180, "180 dias"),
(PRAZO_OUTRO, "Outro"),
)
documento_recebimento = models.ForeignKey(
"DocumentoDeRecebimento",
on_delete=models.CASCADE,
blank=True,
related_name="datas_fabricacao_e_prazos",
)
data_fabricacao = models.DateField("Data Fabricação", blank=True, null=True)
data_validade = models.DateField("Data Validade", blank=True, null=True)
data_maxima_recebimento = models.DateField(
"Data Máxima de Recebimento", blank=True, null=True
)
prazo_maximo_recebimento = models.CharField(
"Prazo Máximo para Recebimento", choices=PRAZO_CHOICES, max_length=5, blank=True
)
justificativa = models.TextField("Justificativa", blank=True)
def __str__(self):
return f'{self.documento_recebimento.cronograma.numero} - {self.data_fabricacao.strftime("%d/%m/%Y")}'
class Meta:
verbose_name = "Data de Fabricação e Prazo"
verbose_name_plural = "Datas de Fabricação e Prazos"
[docs]
@receiver(pre_save, sender=DataDeFabricaoEPrazo)
def data_maxima_recebimento_pre_save(instance, *_args, **_kwargs):
obj = instance
if (
obj.data_fabricacao
and obj.prazo_maximo_recebimento
and obj.prazo_maximo_recebimento != obj.PRAZO_OUTRO
):
nova_data = obj.data_fabricacao + datetime.timedelta(
days=int(obj.prazo_maximo_recebimento)
)
obj.data_maxima_recebimento = nova_data