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(), ]; } }