Files
rkm-conectasus-ci-sandbox/api/app/Services/Almoxarifado/AlmoxarifadoService.php
Antonio Lopes dos Santos afec302c3e
All checks were successful
build-api-image / build (push) Successful in 51s
init commit
2026-08-04 16:52:02 -03:00

1368 lines
48 KiB
PHP

<?php
namespace App\Services\Almoxarifado;
use App\Enums\Almoxarifado\EntradaTipo;
use App\Enums\Almoxarifado\ReservaMotivo;
use App\Enums\Almoxarifado\SaidaTipo;
use App\Exceptions\Almoxarifado\EstoqueIndisponivelException;
use App\Exceptions\Almoxarifado\EstornoNaoPermitidoException;
use App\Models\Almoxarifado\Entrada;
use App\Models\Almoxarifado\EntradaDocumento;
use App\Models\Almoxarifado\EntradaItem;
use App\Models\Almoxarifado\Reserva;
use App\Models\Almoxarifado\ReservaItem;
use App\Models\Almoxarifado\Saida;
use App\Models\Almoxarifado\SaidaBaixa;
use App\Models\Almoxarifado\SaidaItem;
use App\Models\Almoxarifado\SaidaRequisicao;
use App\Models\Almoxarifado\SaidaTransferencia;
use App\Models\Cadastro\Material\Material;
use App\Services\Service;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class AlmoxarifadoService extends Service
{
private const MARIADB_QUANTIDADE_INSUFICIENTE_CODE = 12340;
public static function saveTransferencia($data, $id = null)
{
$saida = self::getSaidaInstance(SaidaTipo::TRANSFERENCIA, $id);
$estavaFinalizada = $saida->finalizada ?? false;
$finalizouAgora = !$estavaFinalizada && $data['finalizada'];
$itensData = collect($data['itens']);
$itensDataNovos = $itensData->filter(fn ($i) => is_null($i['id']));
$itensDataSalvos = $itensData->filter(fn ($i) => !is_null($i['id']));
$itensDataEstornados = $itensData->filter(fn ($i) => $i['estornado']);
self::deleteSaidaItensExcluidos($saida, $itensDataSalvos);
self::saveEstornoDeTransferencia($itensDataEstornados);
self::validarEstoqueSaidaItens($saida, $itensDataNovos, $finalizouAgora);
if ($finalizouAgora) {
$saida->finalizada = true;
$saida->finalizada_em = Carbon::now();
self::updateSaidaItens($saida);
}
$saida->save();
self::saveSaidaItensNovos($saida, $itensDataNovos);
if (!$estavaFinalizada) {
self::saveSaidaTransferencia($saida, $data['destino']);
}
$saida->refresh();
return $saida;
}
public static function saveEstornoDeTransferencia($saidaItensDataEstornados)
{
$entradaItensDataEstornados = collect([]);
$saidaItensDataEstornados->each(function ($item) use ($entradaItensDataEstornados) {
$saidaItem = SaidaItem::findOrFail($item['id']);
$saidaItem->entradaItensTransferencia->each(function ($entradaItem) use ($entradaItensDataEstornados) {
$entradaItensDataEstornados->push(['id' => $entradaItem->id]);
});
});
self::validarEstornoEntradaItens($entradaItensDataEstornados);
self::estornarEntradaItens($entradaItensDataEstornados);
self::estornarSaidaItens($saidaItensDataEstornados);
}
private static function saveSaidaTransferencia($saida, $data)
{
$transferencia = $saida->transferencia ?? new SaidaTransferencia();
$transferencia->centro_custo_id = $data['centro_custo_id'];
$transferencia->usuario_id = $data['usuario_id'];
$saida->transferencia()->save($transferencia);
}
public static function saveEntradasDeTransferencia($saida)
{
$saida->itens->filter(fn ($i) => !$i->estornado && $i->entradaItensTransferencia->isEmpty())->each(function ($saidaItem) {
$saidaItem->entradaItens->each(function ($entradaItemOrigem) use ($saidaItem) {
$centroCustoDestinoId = $saidaItem->saida->transferencia->centro_custo_id;
$entradaDestino = Entrada::create([
'centro_custo_id' => $centroCustoDestinoId,
'tipo_id' => EntradaTipo::TRANSFERENCIA,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$quantidadeEntrada = $entradaItemOrigem->pivotSaidaItemEntradaItem->quantidade_saida;
$entradaItemDestinoSaved = self::saveEntradaItem($entradaDestino, [
'material_id' => $entradaItemOrigem->material_id,
'quantidade' => $quantidadeEntrada,
'lote' => $entradaItemOrigem->lote,
'validade_lote' => $entradaItemOrigem->validade_lote,
'valor_unitario' => $entradaItemOrigem->valor_unitario,
'valor_total' => $entradaItemOrigem->valor_unitario * $quantidadeEntrada,
'numero_empenho' => $entradaItemOrigem->numero_empenho,
'despesas' => $entradaItemOrigem->despesas,
'numero_autorizacao_fornecimento' => $entradaItemOrigem->numero_autorizacao_fornecimento,
'fabricante_id' => $entradaItemOrigem->fabricante_id,
'codigo_barras' => $entradaItemOrigem->codigo_barras,
]);
$saidaItem->entradaItensTransferencia()->attach($entradaItemDestinoSaved->id);
$entradaDestino->refresh();
});
});
}
public static function saveAjusteDeEstoqueDeInventario($itens)
{
$itens->each(function ($item) {
$material = Material::findOrFail($item['material_id']);
$estoque = self::getEstoque($material->id);
$entradas = collect($estoque['por_entrada'])
->where('lote', $item['lote'])
->when($item['validade_lote'], fn ($q, $v) => $q->where('validade_lote', $v->format('d/m/Y')));
$estoqueDoLote = $entradas->sum('estoque');
if ($item['contagem'] > $estoqueDoLote) {
self::saveEntradaDeInventario($item['contagem'] - $estoqueDoLote, [
'material_id' => $material->id,
'integracao_bnafar_medicamento_id' => $item['integracao_bnafar_medicamento_id'],
'lote' => $item['lote'],
'validade_lote' => $item['validade_lote']
]);
}
else if ($item['contagem'] < $estoqueDoLote) {
self::saveSaidaDeInventario($estoqueDoLote - $item['contagem'], [
'material_id' => $material->id,
'lote' => $item['lote'],
'validade_lote' => $item['validade_lote']
], true);
}
});
}
public static function saveEntradaInicial($entradasData)
{
$centroCustoIdLogado = self::getCurrentCentroCusto()->id;
$entradasData->each(function ($entradaData) use ($centroCustoIdLogado) {
$entrada = new Entrada([
'centro_custo_id' => $centroCustoIdLogado,
'tipo_id' => EntradaTipo::INICIAL,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$entrada->save();
if (isset($entradaData['fornecedor_id'])) {
self::saveEntradaDocumento($entrada, [
'fornecedor_id' => $entradaData['fornecedor_id'],
'tipo_id' => null,
'numero_pedido' => null,
'numero' => null,
'data' => null,
]);
}
self::saveEntradaItem($entrada, [
'material_id' => $entradaData['material_id'],
'integracao_bnafar_medicamento_id' => $entradaData['integracao_bnafar_medicamento_id'] ?? null,
'quantidade' => $entradaData['quantidade'],
'lote' => $entradaData['lote'],
'validade_lote' => $entradaData['validade_lote'],
'valor_unitario' => $entradaData['valor_unitario'],
'valor_total' => $entradaData['valor_total'],
'numero_empenho' => null,
'despesas' => null,
'numero_autorizacao_fornecimento' => null,
'fabricante_id' => $entradaData['fabricante_id'],
'codigo_barras' => $entradaData['codigo_barras'],
]);
$entrada->refresh();
});
}
private static function saveEntradaDeInventario($quantidadeDivergente, $data)
{
$entrada = new Entrada([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => EntradaTipo::INVENTARIO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$entrada->save();
$valorUnitario = self::getUltimoValorUnitarioDoMaterial(
$data['material_id'],
$data['lote'] ?? null,
$data['validade_lote'] ?? null
);
$valorTotal = $quantidadeDivergente * $valorUnitario;
self::saveEntradaItem($entrada, [
'material_id' => $data['material_id'],
'integracao_bnafar_medicamento_id' => $data['integracao_bnafar_medicamento_id'],
'quantidade' => $quantidadeDivergente,
'lote' => $data['lote'] ?? null,
'validade_lote' => $data['validade_lote'] ?? null,
'valor_unitario' => $valorUnitario,
'valor_total' => $valorTotal,
'numero_empenho' => null,
'despesas' => 0,
'numero_autorizacao_fornecimento' => null,
'fabricante_id' => null,
'codigo_barras' => null,
]);
$entrada->refresh();
}
private static function getUltimoValorUnitarioDoMaterial($materialId, $lote = null, $validadeLote = null)
{
return (float) (EntradaItem::where('material_id', $materialId)
->when($lote, fn ($q, $l) => $q->where('lote', $l))
->when($validadeLote, fn ($q, $v) => $q->whereDate('validade_lote', $v))
->whereHas('entrada', function ($query) {
$query->where('centro_custo_id', self::getCurrentCentroCusto()->id);
})
->where('valor_unitario', '>', 0)
->orderByDesc('id')
->first()->valor_unitario ?? 0);
}
public static function saveEntradasDeRequisicao($itensData)
{
$centroCustoIdLogado = self::getCurrentCentroCusto()->id;
$entradaItensSaved = collect();
foreach ($itensData as $data) {
$saidaItem = SaidaItem::findOrFail($data['saida_item_id']);
$itens = $saidaItem->entradaItens()->orderBy('id')->get();
if ($data['possui_entrada_com_lote']) {
$itens = $itens->filter(fn ($i) => !is_null($data['entrada_itens']
->firstWhere('entrada_item_origem_id', $i->id)));
}
else {
$data['entrada_itens'] = collect();
$quantidade = $data['quantidade'];
$i = 0;
while ($quantidade > 0) {
$entradaItem = $itens[$i];
$quantidadeSaida = $entradaItem->pivotSaidaItemEntradaItem->quantidade_saida;
if ($quantidade > $quantidadeSaida) {
$data['entrada_itens']->push([
'entrada_item_origem_id' => $entradaItem->id,
'quantidade' => $quantidadeSaida,
]);
$quantidade -= $quantidadeSaida;
}
else {
$data['entrada_itens']->push([
'entrada_item_origem_id' => $entradaItem->id,
'quantidade' => $quantidade,
]);
$quantidade = 0;
}
$i++;
}
}
$itens->each(function ($entradaItem) use ($data, $entradaItensSaved, $centroCustoIdLogado) {
$entrada = Entrada::create([
'centro_custo_id' => $centroCustoIdLogado,
'tipo_id' => EntradaTipo::REQUISICAO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$entradaItemData = $data['entrada_itens']->firstWhere('entrada_item_origem_id', $entradaItem->id);
$entradaItemSaved = self::saveEntradaItem($entrada, [
'material_id' => $entradaItem->material_id,
'quantidade' => $entradaItemData['quantidade'],
'lote' => $entradaItemData['lote'] ?? null,
'validade_lote' => $entradaItemData['validade_lote'] ?? null,
'valor_unitario' => $entradaItem->valor_unitario,
'valor_total' => $entradaItem->valor_unitario * $entradaItemData['quantidade'],
'numero_empenho' => $entradaItem->numero_empenho,
'despesas' => $entradaItem->despesas,
'numero_autorizacao_fornecimento' => $entradaItem->numero_autorizacao_fornecimento,
'fabricante_id' => $entradaItem->fabricante_id,
'codigo_barras' => $entradaItem->codigo_barras,
]);
$entradaItensSaved->push([
'saida_item_id' => $data['saida_item_id'],
'entrada_item_id' => $entradaItemSaved->id,
]);
$entrada->refresh();
});
}
return $entradaItensSaved;
}
public static function saveEntradaManual($data, $id = null)
{
$entrada = self::getEntradaInstance(EntradaTipo::MANUAL, $id);
$estavaFinalizada = $entrada->finalizada ?? false;
$finalizouAgora = !$estavaFinalizada && $data['finalizada'];
$itensData = collect($data['itens']);
$itensDataSalvos = $itensData->filter(fn ($i) => !is_null($i['id']));
$itensDataEstornados = $itensData->filter(fn ($i) => $i['estornado']);
$itensDataNaoEstornados = $itensData->filter(fn ($i) => !$i['estornado']);
self::validarEstornoEntradaItens($itensDataEstornados);
self::deleteEntradaItensExcluidos($entrada, $itensDataSalvos);
self::estornarEntradaItens($itensDataEstornados);
if ($finalizouAgora) {
$entrada->finalizada = true;
$entrada->finalizada_em = Carbon::now();
}
$entrada->save();
self::saveEntradaItens($entrada, $itensDataNaoEstornados);
self::saveEntradaDocumento($entrada, $data['documento']);
$entrada->refresh();
return $entrada;
}
public static function saveEntradaManualWebService($centroCustoId, $data)
{
$entrada = Entrada::create([
'tipo_id' => EntradaTipo::MANUAL,
'centro_custo_id' => $centroCustoId,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
self::saveEntradaItens($entrada, collect($data['itens']));
self::saveEntradaDocumento($entrada, $data['documento']);
return $entrada->fresh();
}
private static function getEntradaInstance($tipoId, $id = null)
{
return is_null($id)
? new Entrada(['tipo_id' => $tipoId, 'centro_custo_id' => self::getCurrentCentroCusto()->id])
: Entrada::where('centro_custo_id', self::getCurrentCentroCusto()->id)
->where('tipo_id', $tipoId)->findOrFail($id);
}
private static function validarEstornoEntradaItens($itensDataEstornados)
{
$materiaisComSaida = collect();
$itensDataEstornados->each(function ($itemData) use ($materiaisComSaida) {
$item = EntradaItem::findOrFail($itemData['id']);
if (!$item->estornado && $item->has_saidas_finalizadas) {
$materiaisComSaida->push($item->material->descricao);
}
});
if ($materiaisComSaida->isNotEmpty()) {
$materiais = $materiaisComSaida->map(fn ($m) => ['descricao' => $m]);
throw new EstornoNaoPermitidoException(json_encode($materiais));
}
}
private static function deleteEntradaItensExcluidos($entrada, $itensDataSalvos)
{
$entrada->itens->each(function ($item) use ($itensDataSalvos) {
if (!$itensDataSalvos->contains('id', $item->id)) {
$item->delete();
}
});
}
private static function estornarEntradaItens($itensDataEstornados)
{
$itensDataEstornados->each(function ($itemData) {
$item = EntradaItem::findOrFail($itemData['id']);
if (!$item->estornado) {
self::saveSaidaDeEstorno($item);
$item->estornado_em = Carbon::now();
$item->save();
}
});
}
private static function saveSaidaDeEstorno($entradaItem)
{
$saida = new Saida([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::ESTORNO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$saida->save();
$saidaItem = self::saveSaidaItem($saida, [
'material_id' => $entradaItem->material_id,
'escolheu_entrada_item_manualmente' => true,
'entrada_itens' => [
[
'entrada_item_id' => $entradaItem->id,
'quantidade_saida' => $entradaItem->quantidade,
]
],
]);
$entradaItem->saida_item_estornado_id = $saidaItem->id;
$saida->refresh();
return $saida;
}
private static function saveEntradaItens($entrada, $itens)
{
$itens->each(fn ($itemData) => self::saveEntradaItem($entrada, $itemData));
}
private static function saveEntradaItem($entrada, $itemData)
{
$item = isset($itemData['id']) ? EntradaItem::findOrFail($itemData['id']) : new EntradaItem();
$item->entrada_id = $entrada->id;
$item->material_id = $itemData['material_id'];
$item->integracao_bnafar_medicamento_id = $itemData['integracao_bnafar_medicamento_id'] ?? null;
$item->quantidade = $itemData['quantidade'];
$item->lote = $itemData['lote'];
$item->validade_lote = $itemData['validade_lote'];
$item->valor_unitario = $itemData['valor_unitario'];
$item->valor_total = $itemData['valor_total'];
$item->numero_empenho = $itemData['numero_empenho'];
$item->despesas = $itemData['despesas'];
$item->numero_autorizacao_fornecimento = $itemData['numero_autorizacao_fornecimento'];
$item->fabricante_id = $itemData['fabricante_id'];
$item->codigo_barras = $itemData['codigo_barras'];
$item->save();
return $item;
}
private static function saveEntradaDocumento($entrada, $data)
{
$documento = $entrada->documento ?? new EntradaDocumento();
$documento->fornecedor_id = $data['fornecedor_id'];
$documento->origem_programatica_id = $data['origem_programatica_id'] ?? null;
$documento->tipo_id = $data['tipo_id'];
$documento->numero_pedido = $data['numero_pedido'];
$documento->numero = $data['numero'];
$documento->data = $data['data'];
$entrada->documento()->save($documento);
}
public static function saveReservaDeRequisicao($itensData, $id = null)
{
if (!is_null($id)) {
Reserva::findOrFail($id)->itens()->each(fn ($i) => $i->entradaItens()->detach());
Reserva::destroy($id);
}
if ($itensData->isEmpty()) {
return null;
}
self::validarEstoque($itensData);
$reserva = Reserva::create(['motivo_id' => ReservaMotivo::REQUISICAO]);
self::saveReservaItensNovos($reserva, $itensData);
return $reserva->fresh();
}
private static function saveReservaItensNovos($reserva, $itensDataNovos)
{
$itensDataNovos->each(fn ($itemData) => self::saveReservaItem($reserva, $itemData));
}
private static function saveReservaItem($reserva, $itemData)
{
try {
$item = new ReservaItem();
$item->material_id = $itemData['material_id'];
$item->escolheu_entrada_item_manualmente = $itemData['escolheu_entrada_item_manualmente'];
$reserva->itens()->save($item);
$item->entradaItens()->sync(self::getEntradaItensData($item->material_id, $itemData));
return $item;
}
catch (Exception $e) {
if ((int) $e->getCode() === 45000
&& Str::contains($e->getMessage(), self::MARIADB_QUANTIDADE_INSUFICIENTE_CODE)
) {
throw new EstoqueIndisponivelException(json_encode([
['descricao' => Material::findOrFail($itemData['material_id'])->descricao]
]));
}
throw $e;
}
}
public static function saveSaidaDeDispensacaoDaFarmacia($itensData)
{
$saidaItensSaved = collect();
self::validarEstoque($itensData);
$saida = Saida::create([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::DISPENSACAO_FARMACIA,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$itensData->each(function ($itemData) use ($saida, $saidaItensSaved) {
$saidaItemSaved = self::saveSaidaItem($saida, $itemData);
$saidaItensSaved->push([
'receita_item_entrega_id' => $itemData['receita_item_entrega_id'],
'saida_item_id' => $saidaItemSaved->id,
]);
});
return $saidaItensSaved;
}
public static function saveSaidaDeDispensacaoDoAlmoxarifado($itensData)
{
$saidaItensSaved = collect();
self::validarEstoque($itensData);
$saida = Saida::create([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::DISPENSACAO_ALMOXARIFADO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
])->fresh();
$itensData->each(function ($itemData) use ($saida, $saidaItensSaved) {
$saidaItemSaved = self::saveSaidaItem($saida, $itemData);
$saidaItensSaved->push([
'dispensacao_item_id' => $itemData['dispensacao_item_id'],
'saida_item_id' => $saidaItemSaved->id,
]);
});
return $saidaItensSaved;
}
public static function saveSaidaDeRequisicao($itensData, $reservaId = null)
{
if (!is_null($reservaId)) {
Reserva::findOrFail($reservaId)->itens()->each(fn ($i) => $i->entradaItens()->detach());
Reserva::destroy($reservaId);
}
if ($itensData->isEmpty()) {
return null;
}
self::validarEstoque($itensData);
$saida = Saida::create([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::REQUISICAO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
self::saveSaidaItensNovos($saida, $itensData);
$saida->refresh();
return $saida;
}
private static function saveSaidaDeInventario($quantidadeDivergente, $data, $verificarLote = false)
{
$saida = new Saida([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::INVENTARIO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$saida->save();
self::saveSaidaItem($saida, [
'material_id' => $data['material_id'],
'escolheu_entrada_item_manualmente' => false,
'lote' => $data['lote'] ?? null,
'validade_lote' => $data['validade_lote'] ?? null,
'quantidade' => $quantidadeDivergente,
'verificar_lote' => $verificarLote,
]);
$saida->refresh();
return $saida;
}
public static function saveSaidaDeBaixa($data)
{
$saida = new Saida([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::BAIXA,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$itensData = collect([
[
'material_id' => $data['material_id'],
'quantidade' => $data['quantidade'],
'entrada_itens' => $data['entrada_itens'],
'escolheu_entrada_item_manualmente' => $data['escolheu_entrada_item_manualmente'],
]
]);
self::validarEstoque($itensData);
$saida->save();
self::saveSaidaItensNovos($saida, $itensData);
$baixaData = [
'motivo_id' => $data['motivo_id'],
'justificativa' => $data['justificativa'],
'usuario_solicitante_id' => $data['usuario_solicitante_id'],
];
self::saveSaidaBaixa($saida, $baixaData);
$saida->refresh();
return $saida;
}
private static function saveSaidaBaixa($saida, $data)
{
$baixa = $saida->baixa ?? new SaidaBaixa();
$baixa->motivo_id = $data['motivo_id'];
$baixa->justificativa = $data['justificativa'];
$baixa->usuario_solicitante_id = $data['usuario_solicitante_id'];
$saida->baixa()->save($baixa);
}
public static function saveSaidaManual($data, $id = null)
{
$saida = self::getSaidaInstance(SaidaTipo::MANUAL, $id);
$estavaFinalizada = $saida->finalizada ?? false;
$finalizouAgora = !$estavaFinalizada && $data['finalizada'];
$itensData = collect($data['itens']);
$itensDataNovos = $itensData->filter(fn ($i) => is_null($i['id']));
$itensDataSalvos = $itensData->filter(fn ($i) => !is_null($i['id']));
$itensDataEstornados = $itensData->filter(fn ($i) => $i['estornado']);
self::deleteSaidaItensExcluidos($saida, $itensDataSalvos);
self::estornarSaidaItens($itensDataEstornados);
self::validarEstoqueSaidaItens($saida, $itensDataNovos, $finalizouAgora);
if ($finalizouAgora) {
$saida->finalizada = true;
$saida->finalizada_em = Carbon::now();
self::updateSaidaItens($saida);
}
$saida->save();
self::saveSaidaItensNovos($saida, $itensDataNovos);
if (!$estavaFinalizada) {
self::saveSaidaRequisicao($saida, $data['requisicao']);
}
$saida->refresh();
return $saida;
}
public static function saveSaidaDeDispensacaoDoAmbulatorio($itemData)
{
self::validarEstoque(collect([$itemData]));
$saida = Saida::create([
'centro_custo_id' => self::getCurrentCentroCusto()->id,
'tipo_id' => SaidaTipo::DISPENSACAO_AMBULATORIO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
return self::saveSaidaItem($saida, $itemData);
}
private static function getSaidaInstance($tipoId, $id = null)
{
return is_null($id)
? new Saida(['tipo_id' => $tipoId, 'centro_custo_id' => self::getCurrentCentroCusto()->id])
: Saida::where('centro_custo_id', self::getCurrentCentroCusto()->id)
->where('tipo_id', $tipoId)->findOrFail($id);
}
private static function deleteSaidaItensExcluidos($saida, $itensDataSalvos)
{
$saida->itens->each(function ($item) use ($itensDataSalvos) {
if (!$itensDataSalvos->contains('id', $item->id)) {
$item->entradaItens()->detach();
$item->delete();
}
});
}
public static function estornarSaidaItens($itensDataEstornados)
{
$itensDataEstornados->each(function ($itemData) {
$item = SaidaItem::findOrFail($itemData['id']);
if (!$item->estornado) {
self::saveEntradasDeEstorno($item);
$item->estornado_em = Carbon::now();
$item->save();
}
});
}
private static function saveEntradasDeEstorno($saidaItem)
{
$centroCustoIdLogado = $saidaItem['created_centro_custo_id'];
$saidaItem->entradaItens->each(function ($entradaItemOld) use ($centroCustoIdLogado) {
$entrada = new Entrada([
'centro_custo_id' => $centroCustoIdLogado,
'tipo_id' => EntradaTipo::ESTORNO,
'finalizada' => true,
'finalizada_em' => Carbon::now(),
]);
$entrada->save();
$entradaItemNew = self::saveEntradaItem($entrada, [
'material_id' => $entradaItemOld->material_id,
'quantidade' => $entradaItemOld->pivotSaidaItemEntradaItem->quantidade_saida,
'lote' => $entradaItemOld->lote,
'validade_lote' => $entradaItemOld->validade_lote,
'valor_unitario' => $entradaItemOld->valor_unitario,
'valor_total' => $entradaItemOld->valor_total,
'numero_empenho' => $entradaItemOld->numero_empenho,
'despesas' => $entradaItemOld->despesas,
'numero_autorizacao_fornecimento' => $entradaItemOld->numero_autorizacao_fornecimento,
'fabricante_id' => $entradaItemOld->fabricante_id,
'codigo_barras' => $entradaItemOld->codigo_barras,
]);
$entradaItemOld->pivotSaidaItemEntradaItem->entrada_item_estornado_id = $entradaItemNew->id;
$entradaItemOld->pivotSaidaItemEntradaItem->save();
$entrada->refresh();
});
}
private static function validarEstoqueSaidaItens($saida, $itensDataNovos, $validarItensSalvos)
{
$materiaisSemEstoque = self::filterMateriaisSemEstoqueDispovivel($itensDataNovos);
if ($validarItensSalvos) {
$saida->itens->each(function ($saidaItem) use ($materiaisSemEstoque) {
$estoque = self::getEstoque($saidaItem->material->id);
$estoqueTotal = $estoque['total'];
$estoquePorEntrada = collect($estoque['por_entrada']);
if ($saidaItem->quantidade > $estoqueTotal) {
$materiaisSemEstoque->push($saidaItem->material->descricao);
} else if ($saidaItem->escolheu_entrada_item_manualmente) {
foreach ($saidaItem->entradaItens as $entradaItem) {
$estoqueEntradaItem = $estoquePorEntrada
->where('entrada_item_id', $entradaItem->id)
->first();
if (
is_null($estoqueEntradaItem)
|| $entradaItem->pivotSaidaItemEntradaItem->quantidade_saida > $estoqueEntradaItem['estoque']
) {
$materiaisSemEstoque->push($saidaItem->material->descricao);
return true;
}
}
}
});
}
if ($materiaisSemEstoque->isNotEmpty()) {
$materiais = $materiaisSemEstoque->map(fn ($m) => ['descricao' => $m]);
throw new EstoqueIndisponivelException(json_encode($materiais));
}
}
private static function updateSaidaItens($saida)
{
foreach ($saida->itens as $saidaItem) {
if ($saidaItem->escolheu_entrada_item_manualmente) {
continue;
}
$refazerSaida = false;
$estoquePorEntrada = collect(self::getEstoquePorEntradas($saidaItem->material_id));
foreach ($saidaItem->entradaItens as $entradaItem) {
$estoqueEntradaItem = $estoquePorEntrada
->where('entrada_item_id', $entradaItem->id)
->first();
if (is_null($estoqueEntradaItem)
|| $entradaItem->pivotSaidaItemEntradaItem->quantidade_saida > $estoqueEntradaItem['estoque']
) {
$refazerSaida = true;
break;
}
}
if ($refazerSaida) {
$entradaItens = self::getEntradaItensData(
$saidaItem->material_id,
[
'escolheu_entrada_item_manualmente' => $saidaItem->escolheu_entrada_item_manualmente,
'quantidade' => $saidaItem->quantidade
]
);
$saidaItem->entradaItens()->sync($entradaItens);
}
}
}
private static function saveSaidaItensNovos($saida, $itensDataNovos)
{
$itensDataNovos->each(fn ($itemData) => self::saveSaidaItem($saida, $itemData));
}
private static function saveSaidaItem($saida, $itemData)
{
try {
$item = new SaidaItem();
$item->material_id = $itemData['material_id'];
$item->escolheu_entrada_item_manualmente = $itemData['escolheu_entrada_item_manualmente'];
$saida->itens()->save($item);
$item->entradaItens()->sync(self::getEntradaItensData($item->material_id, $itemData));
return $item;
}
catch (Exception $e) {
if ((int) $e->getCode() === 45000
&& Str::contains($e->getMessage(), self::MARIADB_QUANTIDADE_INSUFICIENTE_CODE)
) {
throw new EstoqueIndisponivelException(json_encode([
['descricao' => Material::findOrFail($itemData['material_id'])->descricao]
]));
}
throw $e;
}
}
private static function saveSaidaRequisicao($saida, $data)
{
$requisicao = $saida->requisicao ?? new SaidaRequisicao();
$requisicao->centro_custo_id = $data['centro_custo_id'];
$requisicao->usuario_id = $data['usuario_id'];
$requisicao->numero = $data['numero'];
$requisicao->data = $data['data'];
$saida->requisicao()->save($requisicao);
}
private static function validarEstoque($itensDataNovos)
{
$materiaisSemEstoque = self::filterMateriaisSemEstoqueDispovivel($itensDataNovos);
if ($materiaisSemEstoque->isNotEmpty()) {
$materiais = $materiaisSemEstoque->map(fn ($m) => ['descricao' => $m]);
throw new EstoqueIndisponivelException(json_encode($materiais));
}
}
private static function filterMateriaisSemEstoqueDispovivel($itensData)
{
$materiaisSemEstoque = collect();
$itensData->each(function ($itemData) use ($materiaisSemEstoque) {
$material = Material::findOrFail($itemData['material_id']);
$estoque = self::getEstoque($material->id);
$estoqueTotal = $estoque['total'];
$estoquePorEntrada = collect($estoque['por_entrada']);
if (((int) $itemData['quantidade']) > $estoqueTotal) {
$materiaisSemEstoque->push($material->descricao);
}
else if ($itemData['escolheu_entrada_item_manualmente']) {
foreach ($itemData['entrada_itens'] as $entradaItemData) {
$estoqueEntradaItem = $estoquePorEntrada
->where('entrada_item_id', $entradaItemData['entrada_item_id'])
->first();
if (is_null($estoqueEntradaItem)
|| ((int) $entradaItemData['quantidade_saida']) > $estoqueEntradaItem['estoque']
) {
$materiaisSemEstoque->push($material->descricao);
return true;
}
}
}
});
return $materiaisSemEstoque;
}
public static function getEntradaItensData($materialId, $itemData)
{
$entradaItens = [];
if ($itemData['escolheu_entrada_item_manualmente']) {
foreach ($itemData['entrada_itens'] as $entradaItem) {
$entradaItens[$entradaItem['entrada_item_id']] = [
'quantidade_saida' => (int) $entradaItem['quantidade_saida']
];
}
}
else {
$estoquePorEntrada = collect(self::getEstoquePorEntradas($materialId));
if (isset($itemData['lote'])) {
$estoquePorEntrada = $estoquePorEntrada->where('lote', $itemData['lote']);
}
else if ($itemData['verificar_lote'] ?? false) {
$estoquePorEntrada = $estoquePorEntrada->whereNull('lote');
}
if (isset($itemData['validade_lote'])) {
$estoquePorEntrada = $estoquePorEntrada
->where('validade_lote', $itemData['validade_lote']->format('d/m/Y'));
}
$estoquePorEntrada = $estoquePorEntrada->values()->all();
$quantidade = (int) $itemData['quantidade'];
$i = 0;
while ($quantidade > 0) {
$entradaItem = $estoquePorEntrada[$i];
if ($quantidade > $entradaItem['estoque']) {
$entradaItens[$entradaItem['entrada_item_id']] = [
'quantidade_saida' => $entradaItem['estoque']
];
$quantidade -= $entradaItem['estoque'];
}
else {
$entradaItens[$entradaItem['entrada_item_id']] = [
'quantidade_saida' => $quantidade
];
$quantidade = 0;
}
$i++;
}
}
return $entradaItens;
}
public static function getReservas($materialId, $centroCustoId = null)
{
if (is_null($centroCustoId)) {
$centroCustoId = self::getCurrentCentroCusto()->id;
}
$reservaTotal = 0;
$reservas = collect();
Entrada::with([
'itens' => function ($query) use ($materialId) {
$query
->where('material_id', $materialId)
->whereHas('reservaItens');
}
])
->where('finalizada', true)
->where('centro_custo_id', $centroCustoId)
->get()
->each(function ($entrada) use (&$reservaTotal, $reservas) {
$entrada->itens->each(function ($entradaItem) use (&$reservaTotal, $reservas) {
$estoque = (int) $entradaItem->quantidade;
$entradaItem->saidaItens
->filter(fn ($s) => $s->saida->finalizada)
->each(function ($saidaItem) use (&$estoque) {
$estoque -= (int) $saidaItem->pivotSaidaItemEntradaItem->quantidade_saida;
});
$reservado = 0;
$reservasPorRequisicao = collect();
$entradaItem->reservaItens
->filter(fn ($reservaItem) =>
$reservaItem->requisicaoRespostaItem &&
$reservaItem->requisicaoRespostaItem->requisicaoItem &&
$reservaItem->requisicaoRespostaItem->requisicaoItem->requisicao
)
->each(function ($reservaItem) use (&$reservado, $reservasPorRequisicao) {
$requisicao = $reservaItem->requisicaoRespostaItem->requisicaoItem->requisicao;
$quantidade = (int) $reservaItem->pivotReservaItemEntradaItem->quantidade_saida;
$reservasPorRequisicao->push(collect([
'requisicao_id' => $requisicao->id,
'requisicao' => [
'id' => $requisicao->id,
'numero' => $requisicao->numero,
],
'reservado' => $quantidade,
]));
$reservado += $quantidade;
});
$validadeLoteFormatted = $entradaItem->validade_lote
? $entradaItem->validade_lote->format('d/m/Y') : null;
$reserva = $reservas
->where('fornecedor_id', $entradaItem->entrada->documento->fornecedor->id ?? null)
->where('lote', $entradaItem->lote)
->where('validade_lote_formatted', $validadeLoteFormatted)
->first();
if (is_null($reserva)) {
$reserva = collect([
'fornecedor_id' => $entradaItem->entrada->documento->fornecedor->id ?? null,
'fornecedor' => [
'id' => $entradaItem->entrada->documento->fornecedor->id ?? null,
'nome_fantasia' => $entradaItem->entrada->documento->fornecedor->nome_fantasia ?? null,
],
'lote' => $entradaItem->lote,
'validade_lote_formatted' => $validadeLoteFormatted,
'validade_lote' => $entradaItem->validade_lote,
'estoque' => 0,
'reservado' => 0,
'estoque_disponivel' => 0,
'reservas_por_requisicao' => collect(),
]);
$reservas->push($reserva);
}
$reserva['estoque'] += $estoque;
$reserva['reservado'] += $reservado;
$reserva['estoque_disponivel'] = $reserva['estoque'] - $reserva['reservado'];
$reservasPorRequisicao->each(function ($reservaPorRequisicaoData) use ($reserva) {
$reservaPorRequisicao = $reserva['reservas_por_requisicao']
->where('requisicao_id', $reservaPorRequisicaoData['requisicao_id'])
->first();
if (is_null($reservaPorRequisicao)) {
$reservaPorRequisicao = collect([
'requisicao_id' => $reservaPorRequisicaoData['requisicao_id'],
'requisicao' => $reservaPorRequisicaoData['requisicao'],
'reservado' => 0,
]);
$reserva['reservas_por_requisicao']->push($reservaPorRequisicao);
}
$reservaPorRequisicao['reservado'] += $reservaPorRequisicaoData['reservado'];
});
$reservaTotal += $reservado;
});
});
$reservas = $reservas
->sortBy('validade_lote')
->each(function ($reserva) {
if (!is_null($reserva['validade_lote'])) {
$reserva['validade_lote'] = $reserva['validade_lote']->format('d/m/Y');
}
$reserva['reservas_por_requisicao']->each(function ($reservaPorRequisicao) {
unset($reservaPorRequisicao['requisicao_id']);
});
unset(
$reserva['fornecedor_id'],
$reserva['validade_lote_formatted']
);
})
->values()
->all();
return [
'total' => $reservaTotal,
'agrupadas' => $reservas,
];
}
public static function isQuantidadeDisponivel(int $quantidade, $materialId, $centroCustoId = null)
{
return $quantidade <= self::getEstoqueTotal($materialId, $centroCustoId);
}
public static function getEstoqueTotal($materialId, $centroCustoId = null)
{
return self::getEstoque($materialId, $centroCustoId)['total'];
}
public static function getEstoquePorEntradas($materialId, $centroCustoId = null)
{
return self::getEstoque($materialId, $centroCustoId)['por_entrada'];
}
public static function getEstoque($materialId, $centroCustoId = null, $comReservaItemId = null)
{
if (is_null($centroCustoId)) {
$centroCustoId = self::getCurrentCentroCusto()->id;
}
$selectEstoque = "
COALESCE((
COALESCE(entrada_item.quantidade, 0)
-
COALESCE((
SELECT SUM(almoxarifado_saida_item_entrada_item.quantidade_saida)
FROM almoxarifado_saida_item_entrada_item
JOIN almoxarifado_saida_item
ON almoxarifado_saida_item.id = almoxarifado_saida_item_entrada_item.saida_item_id
JOIN almoxarifado_saida
ON almoxarifado_saida.id = almoxarifado_saida_item.saida_id
WHERE almoxarifado_saida_item_entrada_item.entrada_item_id = entrada_item.id
AND almoxarifado_saida.finalizada = 1
), 0)
-
COALESCE((
SELECT SUM(almoxarifado_reserva_item_entrada_item.quantidade_saida)
FROM almoxarifado_reserva_item_entrada_item
JOIN almoxarifado_reserva_item
ON almoxarifado_reserva_item.id = almoxarifado_reserva_item_entrada_item.reserva_item_id
WHERE almoxarifado_reserva_item_entrada_item.entrada_item_id = entrada_item.id
" . (is_null($comReservaItemId) ? '' : "AND almoxarifado_reserva_item.id <> {$comReservaItemId}") . "
), 0)
), 0)
";
$entradaItens = DB::table('almoxarifado_entrada_item as entrada_item')
->join(
'almoxarifado_entrada as entrada',
'entrada.id',
'entrada_item.entrada_id'
)
->leftJoin(
'almoxarifado_entrada_documento as entrada_documento',
'entrada_documento.entrada_id',
'entrada.id'
)
->leftJoin(
'cadastro_fornecedor as fornecedor',
'fornecedor.id',
'entrada_documento.fornecedor_id'
)
->where('entrada_item.material_id', $materialId)
->where('entrada.finalizada', true)
->where('entrada.centro_custo_id', $centroCustoId)
->whereRaw("{$selectEstoque} > 0")
->select(DB::raw("
entrada_item.id as entrada_item_id,
fornecedor.id as fornecedor_id,
fornecedor.nome_fantasia as fornecedor_nome_fantasia,
entrada_item.lote,
entrada_item.validade_lote,
{$selectEstoque} as estoque
"))
->orderBy('entrada_item.id')
->get();
$estoqueTotal = 0;
$entradaItens = $entradaItens->map(function ($entradaItem) use (&$estoqueTotal) {
$estoqueTotal += $entradaItem->estoque;
return collect([
'entrada_item_id' => $entradaItem->entrada_item_id,
'fornecedor' => [
'id' => $entradaItem->fornecedor_id,
'nome_fantasia' => $entradaItem->fornecedor_nome_fantasia,
],
'lote' => $entradaItem->lote,
'validade_lote' => $entradaItem->validade_lote,
'estoque' => $entradaItem->estoque,
]);
});
$semLote = $entradaItens->whereNull('lote')->values();
$comLote = $entradaItens->whereNotNull('lote')
->sortBy([
['validade_lote', 'asc'],
['entrada_item_id', 'asc']
])
->values();
$estoquePorEntrada = $comLote->concat($semLote);
$estoquePorEntrada
->each(function ($estoqueEntrada) {
if (!is_null($estoqueEntrada['validade_lote'])) {
$estoqueEntrada['validade_lote'] = (new Carbon($estoqueEntrada['validade_lote']))->format('d/m/Y');
}
});
return [
'total' => $estoqueTotal,
'por_entrada' => $estoquePorEntrada->all(),
];
}
}