none
Problema al agregar o restar Stock de un sistema de ventas. RRS feed

  • Pregunta

  • Hola , que tengan buen día, tengo un pequeño enredo y se me esta fundiendo el cerebro ajajaja

    tengo hasta ahora 3 tablas solo con primary key, les borre su foreing key por unos errores.

    https://drive.google.com/open?id=1H6VvKOUFDrmlN9_PJF1UBKUNvWg7xhHe

    hasta ahora el botón buscar trae los datos de lo artículos por medio del "CODIGO" y uno debe poner la cantidad para después agregar .

    https://drive.google.com/open?id=14jz_cMZc0SyEsb85SyhfQTA07Ot5nZ72

    nose como implementar que reste o sume en el stock y utilizar esos datos para después sacar un informe con estadísticas.


    jueves, 18 de abril de 2019 20:46

Respuestas

  • Hola Managarm_hati:

    El tema de las foreign key no es, creo tú problema. Pero siempre puedes subsanarlo ahora.

    Alter table add foreign key

    https://stackoverflow.com/questions/10389477/sql-add-foreign-key-to-existing-column

    Normalmente las tablas de ventas, se organizan en lo que se llama maestro-detalle. Donde tienes un maestro que dispone de la información única de la venta, como es el id de cliente, la fecha, en número de operación, el código del vendedor....y muchos otros campos que son únicos para esta venta. También el total del importe total, impuestos etc. pero esto siempre una fila. (Supongo que tu tabla Ventas es básicamente esto)

    Luego esta el detalle, donde se tienen que permitir la repetición de muchas líneas (Tu tabla tableVentas), pero aquí si puede haber una dificultad, porque no puedo repetir la venta de un ítem. Imaginate que estas en un gran supermercado, que el personal de caja ha pasado 35 artículos por el líneal, y en el 36 esta repetido, (que va a hacer tu software, cada vez que metes un ítem, repasar los que llevas y sumar, y si no esta entonces voy a la base de datos a buscar), y el cliente en su panel visible, no ve esta operación, o al menos no es lo normal. Lo adecuado en este escenario que te planteo es que se puedan repetir líneas.

    Pienso que es más fácil dotar a cada una de las líneas de un autonumérico.

    Al hilo de esto, para tener un stock, tienes que tener al menos compras y ventas. Donde las compras suman y las ventas restan. 

    Aquí hay dos maneras, de enfocar el asunto:

    Opción 1. Tienes una tabla de stock por ítem y almacén (donde este puede ser una tienda, o lo que quieras definir como tal).

    Con esta opción, cuando guardes finalmente una compra o venta, adicionalmente, tienes que sumar o restar el stock de cada una de las líneas en la tabla stock. Y para que no se te estropee el stock, tienes que hacerlo en la misma transacción de la compra o venta. (modo más simple a través de un procedimiento almacenado).

    Opción 2. La que a mi más me gusta. El stock es siempre calculado.

    Realizas una consulta que almacenas como vista y te devuelve la suma de las compras y ventas por articulo y almacén. 

    Cuando guardas la venta, no cambia el stock, este solo se muestra cuando se solicita.

    Espero te ayude

    viernes, 19 de abril de 2019 7:20
  • muchas gracias, logre hacer que se resten los stock, programe para que avisara cuando el stock es cero y cuando la cantidad excede el stock, pero estoy teniendo un error nose si al insertar o al actualizar la resta. 

    Imagen 1 : Datos en el programa

    Imagen 2 : Datos en la Tabla

    Imagen 3: Cantidad que se comprara (desde el programa)

    Imagen 4; Resultado del descuento al Stock en la tabla

    como se puede ver, la resta del primer articulo no corresponde a la cantidad colocada. 

    Tengo mi código en insertar así :

      private void btnCancelar_Click(object sender, EventArgs e)
            {           
                conectarbd.Open();
                SqlCommand cmd;
                cmd = new SqlCommand("INSERT INTO Ventas (Total,stock_cantidad,fecha_venta,num_venta,id_codigo) values( @Total,@stock_cantidad,GETDATE(),@num_venta,@id_Codigo)", conectarbd);
    
                try
                {
    
                    foreach (DataGridViewRow row in dataGridViewVenta.Rows)
                    {
                        cmd.Parameters.Clear();
                        cmd.Parameters.AddWithValue("@Total", Convert.ToString(row.Cells["Total"].Value));
                        cmd.Parameters.AddWithValue("@stock_cantidad", Convert.ToString(row.Cells["Cantidad"].Value));
                        cmd.Parameters.AddWithValue("@num_venta", Convert.ToString(lblNumeroVenta.Text));
                        cmd.Parameters.AddWithValue("@id_codigo", Convert.ToString(row.Cells["Codigo"].Value));
                     
                        cmd.ExecuteNonQuery();
                    }
    
                }
                catch (Exception)
                {
                    MessageBox.Show("error al agregar");
                }
                conectarbd.Close();
    
                int suma = 0;
                foreach (DataGridViewRow row in dataGridViewVenta.Rows)
                {
                    suma += Convert.ToInt32(row.Cells["Total"].Value);
    
                }
    
                try
                {
    
                    lblVuelto.Text = (Convert.ToInt32(textBox1.Text) - suma).ToString();
                }
                catch
                {
                    MessageBox.Show("Coloque el monto con el que paga el cliente, no puede quedar vacio.", "Advertencia", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
    
                try
                {
                    int resta = Convert.ToInt32(lblVuelto.Text);
                    if (resta < 0) throw new NegativeException(string.Format("Faltan {0:C} para pagar el total", resta * -1));
                }
                catch (NegativeException ex)
                {
                    MessageBox.Show(ex.Message);
                }
              
            }

    Tengo el codigo para actualizar y hacer al resta de stock así :

     private void btnLimpiar_Click(object sender, EventArgs e)
            {
                // Restar stock a la cantidad en la BD
                conectarbd.Open();
                cmd = new SqlCommand("UPDATE p SET p.Stock = (p.Stock - v.stock_cantidad) FROM tableVentas P INNER JOIN Ventas V ON (P.Codigo = V.id_Codigo)", conectarbd);
                cmd.ExecuteNonQuery();
                conectarbd.Close();
                if (MessageBox.Show("Ingresar otra Compra?", "Alerta!", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    txtAgrePorCode.Text = "0";
                    txtAgreNombre.Text = "";
                    txtAgreCategoria.Text = "";
                    txtAgrePrecio.Text = "0";
                    txtAgreStock.Text = "0";
                    
                    table.Clear();
                    lblVuelto.Text = "0";
                    lPrecio.Text = "0";
                    textBox1.Text = "0";
                    lblNumeroVenta.Text = (Convert.ToInt32(lblNumeroVenta.Text) + 1).ToString("D7");
      
                }
    
            }

    pensé que las cantidades se sumaban para restar al primer articulo pero no veo que haya puesto suma en ningun lado.

    sábado, 27 de abril de 2019 19:58
  • Hola Managarm_hati:

    Es imposible ayudarte, sino pones bien los modelos de datos.

    En tu sentencia update, dice Stock = (TableVentas.Stock - Ventas.stock_cantidad)

    donde Ventas.Codigo = TableVentas.id_Codigo

    En tu imagen de los modelos, esas columnas no existen o no encajan.

    pensé que las cantidades se sumaban para restar al primer articulo pero no veo que haya puesto suma en ningun lado.

    No se puede saber lo que piensa tu cerebro, no estamos dentro de el, no entiendo muy bien lo que quieres decir. Pero lo que has hecho es insertar los registros del datagrid cuando pulsas un botón que se llama "cancelar"

    Y luego es el botón "Limpiar" el que actualiza el stock???  ese no es el camino más lógico. No puedes hacer que el usuario sea quien modifica el stock. Imagínate que hace la venta, esta se graba y se apaga el ordenador que la ha efectuado directamente. Hay venta modificando stock que ya no tienes físicamente, pero tus tabla de stock no han mermado.

    Esta operación se debiera de realizar en la misma transacción.

    Creo que la opción más fácil, de hacer esto es en un procedimiento almacenado, donde puedes controlar la transacción.

    create procedure insertarDetalle(@total int, @stock_Cantidad int, @num_venta int, @id_codigo int)
    as
    begin try
        begin tran
        INSERT INTO Ventas (Total,stock_cantidad,fecha_venta,num_venta,id_codigo) 
    	   values( @Total,@stock_cantidad,GETDATE(),@num_venta,@id_Codigo);
        
    
        UPDATE p SET p.Stock = (p.Stock - v.stock_cantidad) FROM tableVentas P INNER JOIN Ventas V ON (P.Codigo = V.id_Codigo);
                
    commit tran;
    
    
    end try
    begin catch
        rollback tran;
        declare @mensaje Nvarchar(2048) = 'La operación no se ha podido procesar. Inténtelo de nuevo más tarde, o póngase en contacto con su distribuidor';
        throw 50000, @mensaje,1
    end catch
    
    Y lo que hace tu button_limpiar sobra.

    Y en el otro botón solo tienes que cambiar esto.

     conectarbd.Open();
                SqlCommand cmd;
                cmd = new SqlCommand("insertarDetalle", conectarbd);
                cmd.CommandType = CommandType.StoredProcedure;
                try
                {
    
                    foreach (DataGridViewRow row in dataGridViewVenta.Rows)
                    {
                        cmd.Parameters.Clear();
                        cmd.Parameters.AddWithValue("@Total", Convert.ToString(row.Cells["Total"].Value));
                        cmd.Parameters.AddWithValue("@stock_cantidad", Convert.ToString(row.Cells["Cantidad"].Value));
                        cmd.Parameters.AddWithValue("@num_venta", Convert.ToString(lblNumeroVenta.Text));
                        cmd.Parameters.AddWithValue("@id_codigo", Convert.ToString(row.Cells["Codigo"].Value));
                     
                        cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception)
                {
                    MessageBox.Show("error al agregar");
                }

    Espero te ayude

    domingo, 28 de abril de 2019 6:24
  • Creo que lo solucione, deje el el update en el procedimiento con el siguiente codigo:

        UPDATE p 
    	SET p.Stock = (p.Stock - @stock_Cantidad ) FROM tableVentas P  INNER JOIN Ventas V  ON (P.Codigo = @id_Codigo) ;
    

    quedando con el procedimiento de la siguiente forma:

    create procedure insertarDetalle(@total int, @stock_Cantidad int, @num_venta int, @id_codigo int)
    as
    begin try
        begin tran
        INSERT INTO Ventas (Total,stock_cantidad,fecha_venta,num_venta,id_codigo) 
    	   values( @Total,@stock_cantidad,GETDATE(),@num_venta,@id_Codigo);
        
    
        UPDATE p 
    	SET p.Stock = (p.Stock - @stock_Cantidad ) FROM tableVentas P  INNER JOIN Ventas V  ON (P.Codigo = @id_Codigo) ;
    
    commit tran;
    
    
    end try
    begin catch
        rollback tran;
        declare @mensaje Nvarchar(2048) = 'La operación no se ha podido procesar. Inténtelo de nuevo más tarde.';
        throw 50000, @mensaje,1
    end catch

    martes, 30 de abril de 2019 8:43

Todas las respuestas

  • Hola Managarm_hati:

    El tema de las foreign key no es, creo tú problema. Pero siempre puedes subsanarlo ahora.

    Alter table add foreign key

    https://stackoverflow.com/questions/10389477/sql-add-foreign-key-to-existing-column

    Normalmente las tablas de ventas, se organizan en lo que se llama maestro-detalle. Donde tienes un maestro que dispone de la información única de la venta, como es el id de cliente, la fecha, en número de operación, el código del vendedor....y muchos otros campos que son únicos para esta venta. También el total del importe total, impuestos etc. pero esto siempre una fila. (Supongo que tu tabla Ventas es básicamente esto)

    Luego esta el detalle, donde se tienen que permitir la repetición de muchas líneas (Tu tabla tableVentas), pero aquí si puede haber una dificultad, porque no puedo repetir la venta de un ítem. Imaginate que estas en un gran supermercado, que el personal de caja ha pasado 35 artículos por el líneal, y en el 36 esta repetido, (que va a hacer tu software, cada vez que metes un ítem, repasar los que llevas y sumar, y si no esta entonces voy a la base de datos a buscar), y el cliente en su panel visible, no ve esta operación, o al menos no es lo normal. Lo adecuado en este escenario que te planteo es que se puedan repetir líneas.

    Pienso que es más fácil dotar a cada una de las líneas de un autonumérico.

    Al hilo de esto, para tener un stock, tienes que tener al menos compras y ventas. Donde las compras suman y las ventas restan. 

    Aquí hay dos maneras, de enfocar el asunto:

    Opción 1. Tienes una tabla de stock por ítem y almacén (donde este puede ser una tienda, o lo que quieras definir como tal).

    Con esta opción, cuando guardes finalmente una compra o venta, adicionalmente, tienes que sumar o restar el stock de cada una de las líneas en la tabla stock. Y para que no se te estropee el stock, tienes que hacerlo en la misma transacción de la compra o venta. (modo más simple a través de un procedimiento almacenado).

    Opción 2. La que a mi más me gusta. El stock es siempre calculado.

    Realizas una consulta que almacenas como vista y te devuelve la suma de las compras y ventas por articulo y almacén. 

    Cuando guardas la venta, no cambia el stock, este solo se muestra cuando se solicita.

    Espero te ayude

    viernes, 19 de abril de 2019 7:20
  • muchas gracias, logre hacer que se resten los stock, programe para que avisara cuando el stock es cero y cuando la cantidad excede el stock, pero estoy teniendo un error nose si al insertar o al actualizar la resta. 

    Imagen 1 : Datos en el programa

    Imagen 2 : Datos en la Tabla

    Imagen 3: Cantidad que se comprara (desde el programa)

    Imagen 4; Resultado del descuento al Stock en la tabla

    como se puede ver, la resta del primer articulo no corresponde a la cantidad colocada. 

    Tengo mi código en insertar así :

      private void btnCancelar_Click(object sender, EventArgs e)
            {           
                conectarbd.Open();
                SqlCommand cmd;
                cmd = new SqlCommand("INSERT INTO Ventas (Total,stock_cantidad,fecha_venta,num_venta,id_codigo) values( @Total,@stock_cantidad,GETDATE(),@num_venta,@id_Codigo)", conectarbd);
    
                try
                {
    
                    foreach (DataGridViewRow row in dataGridViewVenta.Rows)
                    {
                        cmd.Parameters.Clear();
                        cmd.Parameters.AddWithValue("@Total", Convert.ToString(row.Cells["Total"].Value));
                        cmd.Parameters.AddWithValue("@stock_cantidad", Convert.ToString(row.Cells["Cantidad"].Value));
                        cmd.Parameters.AddWithValue("@num_venta", Convert.ToString(lblNumeroVenta.Text));
                        cmd.Parameters.AddWithValue("@id_codigo", Convert.ToString(row.Cells["Codigo"].Value));
                     
                        cmd.ExecuteNonQuery();
                    }
    
                }
                catch (Exception)
                {
                    MessageBox.Show("error al agregar");
                }
                conectarbd.Close();
    
                int suma = 0;
                foreach (DataGridViewRow row in dataGridViewVenta.Rows)
                {
                    suma += Convert.ToInt32(row.Cells["Total"].Value);
    
                }
    
                try
                {
    
                    lblVuelto.Text = (Convert.ToInt32(textBox1.Text) - suma).ToString();
                }
                catch
                {
                    MessageBox.Show("Coloque el monto con el que paga el cliente, no puede quedar vacio.", "Advertencia", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
    
                try
                {
                    int resta = Convert.ToInt32(lblVuelto.Text);
                    if (resta < 0) throw new NegativeException(string.Format("Faltan {0:C} para pagar el total", resta * -1));
                }
                catch (NegativeException ex)
                {
                    MessageBox.Show(ex.Message);
                }
              
            }

    Tengo el codigo para actualizar y hacer al resta de stock así :

     private void btnLimpiar_Click(object sender, EventArgs e)
            {
                // Restar stock a la cantidad en la BD
                conectarbd.Open();
                cmd = new SqlCommand("UPDATE p SET p.Stock = (p.Stock - v.stock_cantidad) FROM tableVentas P INNER JOIN Ventas V ON (P.Codigo = V.id_Codigo)", conectarbd);
                cmd.ExecuteNonQuery();
                conectarbd.Close();
                if (MessageBox.Show("Ingresar otra Compra?", "Alerta!", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    txtAgrePorCode.Text = "0";
                    txtAgreNombre.Text = "";
                    txtAgreCategoria.Text = "";
                    txtAgrePrecio.Text = "0";
                    txtAgreStock.Text = "0";
                    
                    table.Clear();
                    lblVuelto.Text = "0";
                    lPrecio.Text = "0";
                    textBox1.Text = "0";
                    lblNumeroVenta.Text = (Convert.ToInt32(lblNumeroVenta.Text) + 1).ToString("D7");
      
                }
    
            }

    pensé que las cantidades se sumaban para restar al primer articulo pero no veo que haya puesto suma en ningun lado.

    sábado, 27 de abril de 2019 19:58
  • Hola Managarm_hati:

    Es imposible ayudarte, sino pones bien los modelos de datos.

    En tu sentencia update, dice Stock = (TableVentas.Stock - Ventas.stock_cantidad)

    donde Ventas.Codigo = TableVentas.id_Codigo

    En tu imagen de los modelos, esas columnas no existen o no encajan.

    pensé que las cantidades se sumaban para restar al primer articulo pero no veo que haya puesto suma en ningun lado.

    No se puede saber lo que piensa tu cerebro, no estamos dentro de el, no entiendo muy bien lo que quieres decir. Pero lo que has hecho es insertar los registros del datagrid cuando pulsas un botón que se llama "cancelar"

    Y luego es el botón "Limpiar" el que actualiza el stock???  ese no es el camino más lógico. No puedes hacer que el usuario sea quien modifica el stock. Imagínate que hace la venta, esta se graba y se apaga el ordenador que la ha efectuado directamente. Hay venta modificando stock que ya no tienes físicamente, pero tus tabla de stock no han mermado.

    Esta operación se debiera de realizar en la misma transacción.

    Creo que la opción más fácil, de hacer esto es en un procedimiento almacenado, donde puedes controlar la transacción.

    create procedure insertarDetalle(@total int, @stock_Cantidad int, @num_venta int, @id_codigo int)
    as
    begin try
        begin tran
        INSERT INTO Ventas (Total,stock_cantidad,fecha_venta,num_venta,id_codigo) 
    	   values( @Total,@stock_cantidad,GETDATE(),@num_venta,@id_Codigo);
        
    
        UPDATE p SET p.Stock = (p.Stock - v.stock_cantidad) FROM tableVentas P INNER JOIN Ventas V ON (P.Codigo = V.id_Codigo);
                
    commit tran;
    
    
    end try
    begin catch
        rollback tran;
        declare @mensaje Nvarchar(2048) = 'La operación no se ha podido procesar. Inténtelo de nuevo más tarde, o póngase en contacto con su distribuidor';
        throw 50000, @mensaje,1
    end catch
    
    Y lo que hace tu button_limpiar sobra.

    Y en el otro botón solo tienes que cambiar esto.

     conectarbd.Open();
                SqlCommand cmd;
                cmd = new SqlCommand("insertarDetalle", conectarbd);
                cmd.CommandType = CommandType.StoredProcedure;
                try
                {
    
                    foreach (DataGridViewRow row in dataGridViewVenta.Rows)
                    {
                        cmd.Parameters.Clear();
                        cmd.Parameters.AddWithValue("@Total", Convert.ToString(row.Cells["Total"].Value));
                        cmd.Parameters.AddWithValue("@stock_cantidad", Convert.ToString(row.Cells["Cantidad"].Value));
                        cmd.Parameters.AddWithValue("@num_venta", Convert.ToString(lblNumeroVenta.Text));
                        cmd.Parameters.AddWithValue("@id_codigo", Convert.ToString(row.Cells["Codigo"].Value));
                     
                        cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception)
                {
                    MessageBox.Show("error al agregar");
                }

    Espero te ayude

    domingo, 28 de abril de 2019 6:24
  • Hola, gracias por la ayuda, cambie y puse el procedimiento almacenado que me mandaste funciona pero los descuentos siguen igual, en el momento agrega bien los datos en la tabla "Ventas" pero al momento de restar y actualizar la tabla con el stock lo hace mal, es como si sumara la cantidad dos veces para después descontarla al stock.


    lunes, 29 de abril de 2019 22:02
  • Hola Managarm_hati:

    ¿No será que el código, se realiza dos veces?

    Pon un punto de interrupción, en la llamada a la función donde se manda al SQL Server ejecutar el procedimiento. Cuando pase la primera vez, vas pulsando f10 y nada más pasar, comprueba los valores que tiene la tabla.

    Luego pulsas F5 y verificas si vuelve a pasar por el mismo sitio.

    Como Depurar

    https://docs.microsoft.com/es-es/visualstudio/debugger/debugger-feature-tour?view=vs-2019

    martes, 30 de abril de 2019 4:09
  • hice la depuración con F11 para ir viendo paso a paso como entran los codigos y el problema esta en el Foreach , recorre tantas veces como artículos se vendan, por ejemplo si se venden 3 artículos recorre 3 veces y cada ves que recorre entran los datos,
    eso quiere decir que si vendo:

     2 Manzana , 3 Peras , 2 Uvas

    se restarían las veces que recorra, tonces ya no descontaría 2  al stock de manzana sino que 6 . 
    debe ser porque el insert con el update estan juntos, por lo que inserta y actualiza las veces que recorra el foreach .
    martes, 30 de abril de 2019 4:29
  • Pero se supone que código es el código del artículo (manzanas). 

    Si es así la update ya no lo ejecutaría para manzanas.

    Pero si no lo es, tienes que pasarle el artículo al que vas a modificar el stock, y la update se hace contra ese parámetro.

    martes, 30 de abril de 2019 5:09
  • Tendría que hacer el procedimiento almacenado con insertar y formar un Trigger con el actualizar ?

    martes, 30 de abril de 2019 5:40
  • No, no utilices triggers, para eso, si tienes el procedure, puedes desencadenarlo desde el mismo. Los triggers, son complicados de gestionar, y su utilización para la lógica de negocio, es digamos no del todo recomendable. Por supuesto que se hace en muchos sitios, pero no es la mejor manera.

    La sentencia update, tiene que apuntar al articulo al que le quieres descontar el stock.

     UPDATE p SET p.Stock = (p.Stock - v.stock_cantidad) FROM tableVentas P INNER JOIN Ventas V ON (P.Codigo = V.id_Codigo);

    Cual es el código de articulo en esa sentencia. si no esta, tiene que ser 

    where .... and codigoArticulo =@xxxx

    martes, 30 de abril de 2019 6:06
  • https://drive.google.com/open?id=1pyqd1FoeC3gpd6TXosDD7xLVjjKcPE99

    lo tengo con el código del articulo pero creo que seria mejor si dejo un WHERE con el nu_venta que es prácticamente el numero de la boleta.


    martes, 30 de abril de 2019 6:34
  • Creo que lo solucione, deje el el update en el procedimiento con el siguiente codigo:

        UPDATE p 
    	SET p.Stock = (p.Stock - @stock_Cantidad ) FROM tableVentas P  INNER JOIN Ventas V  ON (P.Codigo = @id_Codigo) ;
    

    quedando con el procedimiento de la siguiente forma:

    create procedure insertarDetalle(@total int, @stock_Cantidad int, @num_venta int, @id_codigo int)
    as
    begin try
        begin tran
        INSERT INTO Ventas (Total,stock_cantidad,fecha_venta,num_venta,id_codigo) 
    	   values( @Total,@stock_cantidad,GETDATE(),@num_venta,@id_Codigo);
        
    
        UPDATE p 
    	SET p.Stock = (p.Stock - @stock_Cantidad ) FROM tableVentas P  INNER JOIN Ventas V  ON (P.Codigo = @id_Codigo) ;
    
    commit tran;
    
    
    end try
    begin catch
        rollback tran;
        declare @mensaje Nvarchar(2048) = 'La operación no se ha podido procesar. Inténtelo de nuevo más tarde.';
        throw 50000, @mensaje,1
    end catch

    martes, 30 de abril de 2019 8:43