Gerenciamento de Espaços de Armazenagem com C#

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.

Tags: C# SQL Server controle de estoque armazenagem algoritmo de alocação

Publicado em 9-5 06:55