Em sistemas de controle de estoque, é comum que a estrutura de armazenamento seja modelada em prateleiras com múltiplas camadas e divisões. Cada prateleira possui um número máximo de colunas (grid) e linhas (layer). Os itens individuais são registrados em uma tabela de detalhes, ocupando uma posição específica. Para determinar as posições disponíveis para entrada de novos produtos, é necesário percorrer a estrutura e filtrar os locais já ocupados. O algoritmo a seguir implementa essa lógica.
O método ObterLocaisDisponiveis recebe o código de uma linha de armazenamento e uma lista de prateleiras. Ele consulta os detalhes de itans que ainda não receberam baixa e, em seguida, gera a lista de posições livres com base na capacidade máxima de cada prateleira.
public class GerenciadorEstoque
{
public static List<SlotDisponivel> ObterLocaisDisponiveis(string codigoLinha, List<Prateleira> prateleiras)
{
var ocupados = new List<SlotDisponivel>();
var livres = new List<SlotDisponivel>();
foreach (var prateleira in prateleiras)
{
string sql = $"SELECT prateleira_codigo, coluna, camada FROM ITEM_DETALHE " +
$"WHERE prateleira_codigo = '{prateleira.Codigo}' AND codigo_saida IS NULL";
using (var leitor = DatabaseHelper.ExecuteReader(sql))
{
while (leitor.Read())
{
ocupados.Add(new SlotDisponivel
{
CodigoPrateleira = leitor["prateleira_codigo"].ToString(),
Coluna = leitor["coluna"].ToString(),
Camada = leitor["camada"].ToString()
});
}
}
}
foreach (var prateleira in prateleiras)
{
int maxColunas = prateleira.Colunas;
int maxCamadas = prateleira.Camadas;
for (int c = 1; c <= maxColunas; c++)
{
for (int l = 1; l <= maxCamadas; l++)
{
bool ocupado = ocupados.Any(o =>
o.CodigoPrateleira == prateleira.Codigo &&
o.Coluna == c.ToString() &&
o.Camada == l.ToString());
if (!ocupado)
{
livres.Add(new SlotDisponivel
{
CodigoPrateleira = prateleira.Codigo,
NomePrateleira = prateleira.Nome,
Coluna = c.ToString(),
Camada = l.ToString()
});
}
}
}
}
return livres;
}
public static int InserirItemDetalhe(ItemDetalhe item)
{
string sql = @"INSERT INTO ITEM_DETALHE
(codigo_detalhe, codigo_produto, indice_detalhe, codigo_entrada,
prateleira_codigo, camada, coluna, data_fabricacao, data_criacao, excluido)
VALUES
(@codigo_detalhe, @codigo_produto, @indice_detalhe, @codigo_entrada,
@prateleira_codigo, @camada, @coluna, @data_fabricacao, @data_criacao, @excluido)";
SqlParameter[] parametros = {
DatabaseHelper.CriarParametro("@codigo_detalhe", item.CodigoDetalhe, SqlDbType.Char, 32),
DatabaseHelper.CriarParametro("@codigo_produto", item.CodigoProduto, SqlDbType.Char, 32),
DatabaseHelper.CriarParametro("@indice_detalhe", item.IndiceDetalhe, SqlDbType.VarChar, 18),
DatabaseHelper.CriarParametro("@codigo_entrada", item.CodigoEntrada, SqlDbType.Char, 32),
DatabaseHelper.CriarParametro("@prateleira_codigo", item.CodigoPrateleira, SqlDbType.Char, 32),
DatabaseHelper.CriarParametro("@camada", item.Camada, SqlDbType.Int),
DatabaseHelper.CriarParametro("@coluna", item.Coluna, SqlDbType.Int),
DatabaseHelper.CriarParametro("@data_fabricacao", item.DataFabricacao, SqlDbType.DateTime),
DatabaseHelper.CriarParametro("@data_criacao", item.DataCriacao, SqlDbType.DateTime),
DatabaseHelper.CriarParametro("@excluido", item.Excluido, SqlDbType.Int)
};
return DatabaseHelper.ExecuteNonQuery(sql, parametros);
}
}
public class SlotDisponivel
{
public string CodigoPrateleira { get; set; }
public string NomePrateleira { get; set; }
public string Coluna { get; set; }
public string Camada { get; set; }
}
A estrutura de dados que suporta esse algoritmo é composta por tabelas relacionais que representam a hierarquia do armazém. Abaixo, um exemplo de esquema de banco de dados adaptado para esse contexto, com nomes e comentários em português.
-- Tabela de Armazém
CREATE TABLE ARMAZEM (
id_serial INT NOT NULL IDENTITY UNIQUE,
codigo_armazem CHAR(32) NOT NULL PRIMARY KEY,
nome_armazem VARCHAR(64) NOT NULL,
ordem INT NOT NULL DEFAULT 0,
endereco VARCHAR(255),
responsavel VARCHAR(16),
telefone VARCHAR(16),
observacao TEXT,
data_criacao DATETIME NOT NULL DEFAULT GETDATE(),
excluido INT NOT NULL DEFAULT 0,
data_exclusao DATETIME
);
-- Setor do armazém
CREATE TABLE SETOR (
id_serial INT NOT NULL IDENTITY UNIQUE,
codigo_setor CHAR(32) NOT NULL PRIMARY KEY,
nome_setor VARCHAR(64) NOT NULL,
codigo_armazem CHAR(32) NOT NULL REFERENCES ARMAZEM(codigo_armazem),
ordem INT NOT NULL DEFAULT 0,
observacao TEXT,
data_criacao DATETIME NOT NULL DEFAULT GETDATE(),
excluido INT NOT NULL DEFAULT 0,
data_exclusao DATETIME
);
-- Corredor
CREATE TABLE CORREDOR (
id_serial INT NOT NULL IDENTITY UNIQUE,
codigo_corredor CHAR(32) NOT NULL PRIMARY KEY,
nome_corredor VARCHAR(64) NOT NULL,
codigo_setor CHAR(32) NOT NULL REFERENCES SETOR(codigo_setor),
ordem INT NOT NULL DEFAULT 0,
codigo_barras VARCHAR(18) NOT NULL,
observacao TEXT,
data_criacao DATETIME NOT NULL DEFAULT GETDATE(),
excluido INT NOT NULL DEFAULT 0,
data_exclusao DATETIME
);
-- Prateleira
CREATE TABLE PRATELEIRA (
id_serial INT NOT NULL IDENTITY UNIQUE,
codigo_prateleira CHAR(32) NOT NULL PRIMARY KEY,
nome_prateleira VARCHAR(64) NOT NULL,
codigo_corredor CHAR(32) NOT NULL REFERENCES CORREDOR(codigo_corredor),
ordem INT NOT NULL DEFAULT 0,
codigo_barras VARCHAR(18) NOT NULL,
camadas INT NOT NULL,
colunas INT NOT NULL DEFAULT 0,
capacidade_kg INT,
volume_m3 FLOAT,
observacao TEXT,
data_criacao DATETIME NOT NULL DEFAULT GETDATE(),
excluido INT NOT NULL DEFAULT 0,
data_exclusao DATETIME
);
-- Produto
CREATE TABLE PRODUTO (
id_serial INT NOT NULL IDENTITY UNIQUE,
codigo_produto CHAR(32) NOT NULL PRIMARY KEY,
nome_produto VARCHAR(64) NOT NULL,
codigo_barras VARCHAR(18),
categoria_cod CHAR(32) NOT NULL, -- referência a tabela de categoria
estoque_min INT NOT NULL DEFAULT 0,
estoque_max INT NOT NULL DEFAULT 0,
validade_meses INT NOT NULL DEFAULT 0,
observacao TEXT,
data_criacao DATETIME NOT NULL DEFAULT GETDATE(),
excluido INT NOT NULL DEFAULT 0,
data_exclusao DATETIME
);
-- Detalhe do item (posição ocupada)
CREATE TABLE ITEM_DETALHE (
id_serial INT NOT NULL IDENTITY UNIQUE,
codigo_detalhe CHAR(32) NOT NULL PRIMARY KEY,
codigo_produto CHAR(32) NOT NULL REFERENCES PRODUTO(codigo_produto),
codigo_barras VARCHAR(18),
codigo_entrada CHAR(32),
codigo_saida CHAR(32),
prateleira_codigo CHAR(32) REFERENCES PRATELEIRA(codigo_prateleira),
camada INT NOT NULL DEFAULT 0,
coluna INT NOT NULL DEFAULT 0,
data_fabricacao DATETIME,
data_criacao DATETIME NOT NULL DEFAULT GETDATE(),
excluido INT NOT NULL DEFAULT 0,
data_exclusao DATETIME
);
O algoritmo de determinação de locais livres percorre todas as prateleiras de uma linha, consulta os registros ativos em ITEM_DETALHE e, com base nos limites de colunas e camadas, identifica as combinações ainda não utilizadas. Essa abordagem é fundamental para a correta alocação de novos itens no estoque.