ospos/ospos-5-DEVEL-2-procs-min-sqls-es.md
mckaygerhard c7fd3942c5 osposweb: cliente movil offline: datos y fotos de la vetna con dos items y dos pagos
* enlace al sql directo en el git de documentos
* enlaces y fotos directo en el git de documentos
2018-05-28 17:10:32 -04:00

47 KiB

Proceso minimo bajo nivel de querys ejecutados

Ospos es uns aplicacino web, por lo que muchos SQL se repiten entre cada proceso este documento sirve como guia para realizar un API bajo nivel y asi hacer un cliente web o un cliente app movil.

Los archivos completos usados para esto estan en https://gitlab.com/osposweb/osposweb/issues/43

  • 1 Consultas de verificacion
  • 2 Consultas de crear item
  • 3 Consultas de crear customer
  • 4.1 Consultas de venta en efectivo un solo pago
  • 4.2 Consultas de venta a credito varios pagos

ESTE ES UN TRABAJO EN PROGRESO, puede tener cambios o errores intencionales

1 Consultas de verificaion comunes

Dato es una aplicacion web, cada vez se realiza algo es un request y este por ende implica consultas de verificacion:

session

Siempre cada vez se realiza cualquier accion realiza dos consultas de verificiacon de sesion id estos datos se emplean a nivel de php para corroborar es la activa sino lo saca:

IMPORTANTE usando php se extrae/guarda el id del usuario (en este caso id=1)

-- VERIFICACION DE SESSION INICIADA Y ACTIVA:

SELECT GET_LOCK('864b7b0304ad61d61f838f6817c045d4', 300) AS ci_session_lock 

SELECT `data` FROM `ospos_sessions`
WHERE `id` = 'spf87in54i6ihjo9nks5vv6c2n8obb8q' AND `ip_address` = '127.0.0.1' 

employee

El usuario que opera es el employee y despues de validad la sesion valida este mismo, una vez validado empieza validar sus permisos y despues sus accesos.

El sistema tiene un employee y este el unico employee es el usuario admin de id = 1.

IMPORTANTE aqui el usuario employee id 1 es el admin y esta en el objeto session.


-- VERIFICACION DE SI ESTE USUARIO AUN ES VALIDO (pudo haberse cambiado o eliminado en el proceso)

SELECT *
FROM `ospos_employees`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_employees`.`person_id`
WHERE `ospos_employees`.`person_id` = '1' 

-- VERIFICACION DE PERMISOS PARA EL USUARIO ACTIVO: EL EMPLOYEE

SELECT *
FROM `ospos_grants`
WHERE `permission_id` LIKE 'customers%' ESCAPE '!' AND `person_id` = '1' 

SELECT *
FROM `ospos_permissions`
WHERE `permission_id` LIKE 'customers!_%' ESCAPE '!' 

-- VERIFICACION DE ACCESOS PARA EL USUARIO ACTIVO: EMPLOYEE ID 1

SELECT *
FROM `ospos_modules`
JOIN `ospos_permissions` ON `ospos_permissions`.`permission_id` = `ospos_modules`.`module_id`
JOIN `ospos_grants` ON `ospos_permissions`.`permission_id` = `ospos_grants`.`permission_id`
WHERE `person_id` = '1' AND `menu_group` IN('home', 'both') AND `sort` !=0
ORDER BY `sort` ASC 

-- (OPCIONAL) VERIFICACIONES SI ESTA EN VENTA O EN INGRESO (cuadno se esta en modulo sale o return)

SELECT *
FROM `ospos_dinner_tables`
WHERE `status` =0 AND `deleted` =0 

SELECT *
FROM `ospos_stock_locations`
JOIN `ospos_permissions` AS `permissions` ON `permissions`.`location_id` = `ospos_stock_locations`.`location_id`
JOIN `ospos_grants` AS `grants` ON `grants`.`permission_id` = `permissions`.`permission_id`
WHERE `person_id` = '1' AND  `permissions`.`permission_id` LIKE 'sales%' ESCAPE '!' AND `deleted` =0 

-- (OPCIONAL) OBTENCION DE DATOS DE EL USUARIO ACTUAL (solo si va ver sus detalles)

SELECT *
FROM `ospos_employees`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_employees`.`person_id`
WHERE `ospos_employees`.`person_id` = '1' 

SELECT *
FROM `ospos_people`
WHERE `person_id` = 1

-- OBTENCION DE DATOS DE CONFIGURACION DE EL SISTEMA OSPOS

SELECT *
FROM `ospos_app_config`
ORDER BY `key` ASC 

-- (OPCIONAL) SE VERIFICAN SI HAY VENTAS PENDEINTES PARA EL EMPLEADO esto cuando esta en plena venta

SELECT * 
FROM `ospos_stock_locations`
JOIN `ospos_permissions` AS `permissions` ON `permissions`.`location_id` = `ospos_stock_locations`.`location_id`
JOIN `ospos_grants` AS `grants` ON `grants`.`permission_id` = `permissions`.`permission_id`
WHERE `person_id` = '1' AND  `permissions`.`permission_id` LIKE 'sales%' ESCAPE '!' AND `deleted` =0 

customers

El cliente es el customer y un employee puede alterarlo si tiene permisos en el modulo de customers, despues de validar la sesion valida este mismo, y antes de modificar usuarios sean cliente o empleados una vez validado empieza validar sus permisos y despues sus accesos

Por lo que los querys respectivos de busqueda, el cliente usado es de id = 2 ya que como se sabe employee y customer son realmente partes de people y sus id son unicos en dicha tabla, ya que el id 1 es un employee por eso el id 2 es el cliente.

El cliente tiene id 2 porque es el segundo peoplecreado en el sistema que solo tiene dos, un cliente y un employee y este el unico employee es el usuario admin de id = 1.

IMPORTANTE aqui el usuario employee id 1 es el admin y esta en el objeto session.

NOTA como employee y customer son "personas" sus id son unicos, derivan de person

Aqui antes de ver el detalle de el cliente "2" verifica el empleado y despeus la data del cleinte, notese que siempre enlaza la tabla de personas people ya que detalles como nombre y direccion son datos comunes de ambos actores, employee y customer por igual:


SELECT *
FROM `ospos_employees`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_employees`.`person_id`
WHERE `ospos_employees`.`person_id` = '1' 

SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = 2 

SELECT *
FROM `ospos_people`
WHERE `person_id` = 2

2 Consultas de creacion de item

Un item es un producto para vender, no se guarda en sesion como la venta, se inserta directo la data, cosa contraria a si es un kit de items donde primero se reunen la data y despues se realiza la insercion en la base de datos una vez la data esta armada

Datos empleados en la pantalla de items:

  • Item name : item1
  • Category: cat1
  • Stock type: Stock
  • Item type: Standard
  • Supplier: none (combo box)
  • Wholesale price: 10.00
  • Retail price: 15.00
  • Tax 1: 1 (%)
  • Tax 2: 2 (%)
  • Quantity stock: 100
  • Receibing Quantity: 50
  • Reorder level: 70

item check

Igual que otros casos realiza verificaciones primero, de locacion de stock y de existencias, esto para cuando este en pantalla de llenado de datos precarge los combo boxes de seleccion:


SELECT *
FROM `ospos_stock_locations`
JOIN `ospos_permissions` AS `permissions` ON `permissions`.`location_id` = `ospos_stock_locations`.`location_id`
JOIN `ospos_grants` AS `grants` ON `grants`.`permission_id` = `permissions`.`permission_id`
WHERE `person_id` = '1' AND `deleted` =0 LIMIT 1 

SELECT *
FROM `ospos_stock_locations`
JOIN `ospos_permissions` AS `permissions` ON `permissions`.`location_id` = `ospos_stock_locations`.`location_id`
JOIN `ospos_grants` AS `grants` ON `grants`.`permission_id` = `permissions`.`permission_id`
WHERE `person_id` = '1' AND  `permissions`.`permission_id` LIKE 'items%' ESCAPE '!' AND `deleted` =0 

item list

Esto ocurre en la pantalla de items donde se listan los productos, antes y cuando se muestra la pantalla de llenado y su disponibilidad, se preparan los datos al pulsar el boton:


SELECT `items`.`item_id` as `item_id`, MAX(items.name) as name, MAX(items.category) as category, MAX(items.supplier_id) as supplier_id, MAX(items.item_number) as item_number, MAX(items.description) as description, MAX(items.cost_price) as cost_price, MAX(items.unit_price) as unit_price, MAX(items.reorder_level) as reorder_level, MAX(items.receiving_quantity) as receiving_quantity, MAX(items.pic_filename) as pic_filename, MAX(items.allow_alt_description) as allow_alt_description, MAX(items.is_serialized) as is_serialized, MAX(items.deleted) as deleted, MAX(items.custom1) as custom1, MAX(items.custom2) as custom2, MAX(items.custom3) as custom3, MAX(items.custom4) as custom4, MAX(items.custom5) as custom5, MAX(items.custom6) as custom6, MAX(items.custom7) as custom7, MAX(items.custom8) as custom8, MAX(items.custom9) as custom9, MAX(items.custom10) as custom10, MAX(suppliers.person_id) as person_id, MAX(suppliers.company_name) as company_name, MAX(suppliers.agency_name) as agency_name, MAX(suppliers.account_number) as account_number, MAX(suppliers.deleted) as deleted, MAX(inventory.trans_id) as trans_id, MAX(inventory.trans_items) as trans_items, MAX(inventory.trans_user) as trans_user, MAX(inventory.trans_date) as trans_date, MAX(inventory.trans_comment) as trans_comment, MAX(inventory.trans_location) as trans_location, MAX(inventory.trans_inventory) as trans_inventory, MAX(item_quantities.item_id) as qty_item_id, MAX(item_quantities.location_id) as location_id, MAX(item_quantities.quantity) as quantity
FROM `ospos_items` as `items`
LEFT JOIN `ospos_suppliers` as `suppliers` ON `suppliers`.`person_id` = `items`.`supplier_id`
JOIN `ospos_inventory` as `inventory` ON `inventory`.`trans_items` = `items`.`item_id`
JOIN `ospos_item_quantities` as `item_quantities` ON `item_quantities`.`item_id` = `items`.`item_id`
WHERE `location_id` = '1'
AND DATE_FORMAT(trans_date, "%Y-%m-%d") BETWEEN '2010-01-01' AND '2018-05-28' /* desde el dia del install hasta el dia actual*/
AND `items`.`deleted` =0
GROUP BY `items`.`item_id` LIMIT 25 /* nota como es primera pagina, 25 items por pantalla */

SELECT COUNT(DISTINCT items.item_id) as count
FROM `ospos_items` as `items`
LEFT JOIN `ospos_suppliers` as `suppliers` ON `suppliers`.`person_id` = `items`.`supplier_id`
JOIN `ospos_inventory` as `inventory` ON `inventory`.`trans_items` = `items`.`item_id`
JOIN `ospos_item_quantities` as `item_quantities` ON `item_quantities`.`item_id` = `items`.`item_id`
WHERE `location_id` = '1' 
AND DATE_FORMAT(trans_date, "%Y-%m-%d") BETWEEN '2010-01-01' AND '2018-05-28' /* desde el dia del install hasta el dia actual*/ 
AND `items`.`deleted` =0 

SELECT *
FROM `ospos_suppliers`
JOIN `ospos_people` ON `ospos_suppliers`.`person_id` = `ospos_people`.`person_id`
WHERE `deleted` =0
ORDER BY `company_name` ASC 

SELECT *
FROM `ospos_stock_locations`
JOIN `ospos_permissions` AS `permissions` ON `permissions`.`location_id` = `ospos_stock_locations`.`location_id`
JOIN `ospos_grants` AS `grants` ON `grants`.`permission_id` = `permissions`.`permission_id`
WHERE `person_id` = '1' AND  `permissions`.`permission_id` LIKE 'items%' ESCAPE '!' AND `deleted` =0 

SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = 1 AND `location_id` = '1' 

SELECT DISTINCT `category`
FROM `ospos_items`
WHERE `category` LIKE '%cat1%' ESCAPE '!' AND `deleted` =0 /* en la pantalla se uso categoria "cat" como palabra completa */
ORDER BY `category` ASC 

NOTA cada vez se escribe en los campos de combo box se realizan querys ajax ejemplo el de categoria

item crear

Despues de pulsar el boton y realizar lso consultas anteriores, se llena los datos del item, al pulsar el boton de crear se inserta en db los datos:

Datos empleados se recuerdan, la pantalla de items:

  • Item name : item1
  • Category: cat1
  • Stock type: Stock
  • Item type: Standard
  • Supplier: none (combo box)
  • Wholesale price: 10.00
  • Retail price: 15.00
  • Tax 1: 1 (%)
  • Tax 2: 2 (%)
  • Quantity stock: 100
  • Receibing Quantity: 50
  • Reorder level: 70

-- INSERCION DEL NUEVO INTEM COMO TAL

INSERT INTO `ospos_items` 
    (
        `name`, `description`, `category`, `item_type`, `stock_type`, `supplier_id`, `item_number`, 
        `cost_price`, `unit_price`, `reorder_level`, `receiving_quantity`, `allow_alt_description`, 
        `is_serialized`, `deleted`, `custom1`, `custom2`, `custom3`, `custom4`, `custom5`, `custom6`, `custom7`, `custom8`, `custom9`, `custom10`, 
        `tax_category_id`
    ) 
    VALUES 
    (
        'item1', '', 'cat1', '0', '0', NULL, NULL, 10, 15, 70, 50, 0, 0, 0, '', '', '', '', '', '', '', '', '', '', ''
    ) 

-- NO REALIZAN UN UPDATE SINO INSERT CON DELETE esto no porque no sepan sino porque usan CI el framework
-- ADEMAS PORQUE SI HAY ITEMS SIMILARES O SI EL TAX CAMBIA SEGUN ITEMS
-- por ejemplo aqui en vez de ser uno son dos, por ende vuela la ifo vieja y pone la nueva que son dos

DELETE FROM `ospos_items_taxes` WHERE `item_id` = 1 

INSERT INTO `ospos_items_taxes` (`name`, `percent`, `item_id`) VALUES ('', 1, 1) 

INSERT INTO `ospos_items_taxes` (`name`, `percent`, `item_id`) VALUES ('', 2, 1) 

-- DESPUES ACTUALIZA LOS INVENTARIOS RESPECTO EL NUEVO ITEM

INSERT INTO `ospos_item_quantities` 
    (`item_id`, `location_id`, `quantity`) 
    VALUES 
    (1, '1', 100) 

INSERT INTO `ospos_inventory` 
    (`trans_date`, `trans_items`, `trans_user`, `trans_location`, `trans_comment`, `trans_inventory`) 
    VALUES 
    ('2018-05-28 08:56:59', 1, '1', '1', 'Manual Edit of Quantity', 100) 

pantalla item

El archvo log sql completo y la pantalla item empleada para esta explicacion:

querylog-crear-item.sql

ospos-item1-example

item create item2

Proceso similar reducido para otro item el segundo, pero usando cambios ligeros en los datos:

Datos empleados en la pantalla de items para item2:

  • Item name : item2
  • Category: cat2
  • Stock type: Stock
  • Item type: Standard
  • Supplier: none (combo box)
  • Wholesale price: 13.00
  • Retail price: 21.00
  • Tax 1: 1 (cantidad)
  • Tax 2: 3 (%)
  • Quantity stock: 200
  • Receiving Quantity: 100
  • Reorder level: 100
-- para el input combo select de suppliers en la pantalla de nuevo item que se rellena
SELECT `ospos_items`.*, `ospos_suppliers`.`company_name`
FROM `ospos_items`
LEFT JOIN `ospos_suppliers` ON `ospos_suppliers`.`person_id` = `ospos_items`.`supplier_id`
WHERE `item_id` = -1 
-- inserta el item en la tabla aun no maneja los taxes ni inventario, solo iniciales
INSERT INTO `ospos_items` 
    (`name`, `description`, `category`, `item_type`, `stock_type`, `supplier_id`, `item_number`, `cost_price`, `unit_price`, `reorder_level`, `receiving_quantity`, `allow_alt_description`, `is_serialized`, `deleted`, `custom1`, `custom2`, `custom3`, `custom4`, `custom5`, `custom6`, `custom7`, `custom8`, `custom9`, `custom10`, `tax_category_id`) 
VALUES 
    ('item2', '', 'cat2', '0', '0', NULL, NULL, 13, 21, 100, 100, 0, 0, 0, '', '', '', '', '', '', '', '', '', '', '') 
-- pudo existir un item anterior con el tax mal relacionado
DELETE FROM `ospos_items_taxes` WHERE `item_id` = 2 
-- relacion de taxes, aqui el de porcentaje, el otro no se donde lo mete
INSERT INTO `ospos_items_taxes` (`name`, `percent`, `item_id`) VALUES ('', 3, 2) 
-- relacion de inventarios: primero verificacion de locaciones
SELECT * FROM `ospos_stock_locations`
JOIN `ospos_permissions` AS `permissions` ON `permissions`.`location_id` = `ospos_stock_locations`.`location_id`
JOIN `ospos_grants` AS `grants` ON `grants`.`permission_id` = `permissions`.`permission_id`
WHERE `person_id` = '1' AND  `permissions`.`permission_id` LIKE 'items%' ESCAPE '!' AND `deleted` =0 
-- relacion de inventario: segubndo verificacion de disponibilidad
SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = 2
AND `location_id` = '1' 
-- insertar relacion inventario con existencias
INSERT INTO `ospos_item_quantities` (`item_id`, `location_id`, `quantity`) VALUES (2, '1', 200) 
-- insertar relacion inventario con movimientos del mismo
INSERT INTO `ospos_inventory` 
    (`trans_date`, `trans_items`, `trans_user`, `trans_location`, `trans_comment`, `trans_inventory`) 
VALUES 
    ('2018-05-28 14:03:01', 2, '1', '1', 'Manual Edit of Quantity', 200) 

3 Consultas de creacion de customer

El customer es un usuario igual que el employee tiene caracteristicas de persona (tabla people) pero no tiene entrada en la tabla employee por ende no inicia sesion.

Aqui el cliente creado resulta que es de id = 2 ya que employee y customer son realmente partes derivadas de people y sus id son unicos en dicha tabla, el nuevo customer se crea con el id 2 ya que el id 1 es un employee y ambos derivan de people por eso el id 2 es el cliente.

Datos empleados en la pantalla de customer o cliente:

  • Registration consent: check
  • First Name: nombre
  • Last Name: apellido
  • Gender: M
  • Email: "correo"
  • Phone Number: 5555557
  • Address 1: direccion1
  • Address 2: direccion2
  • City: ciudad
  • State: estado
  • Country: "repu domin"
  • Comments: comentario
  • Discount: 1 (%)

customer list

Es la pantalla donde entra el modulo de clientes, lista los cleintes, y despues de insertar esta es la pantalla que muestra, pero filtrando el cliente especifico acabado de crear:

No es de mayor relevancia mas que realizar un SQL directo filtrado por id.

IMPORTANTE no se entiende para que esos querys antes crear el customer si es nuevo no tiene ventas.

customer check

Igual que otros casos realiza verificaciones primero, de employee y de customer esto para cuando este en pantalla de llenado de datos precarge los combo boxes de seleccion:

-- verifica no sea uno ya borrado, como es nuevo el id es -1 antes de insertar
SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = -1 /* claro que el id antes de crear aun aqui es -1 */
-- verifica que no exista una persona, claro como es nuevo el id es -1 antes de insertar
SELECT *
FROM `ospos_people`
WHERE `person_id` = -1 /* claro que el id antes de crear aun aqui es -1 */
LIMIT 1 
-- no hay documentacion de esto, aun no se enteinde este sql:
SELECT *
FROM `ospos_customers_packages`
WHERE `deleted` =0 
-- se verifica si no es un cliente temporal con ventas pendientes y el cliente e fue borrado
CREATE TEMPORARY TABLE IF NOT EXISTS ospos_sales_items_temp (INDEX(sale_id))
	    (
		SELECT
		    sales.sale_id AS sale_id,
		    AVG(sales_items.discount_percent) AS avg_discount,
		    SUM(sales_items.quantity_purchased) AS quantity
		FROM ospos_sales AS sales
		INNER JOIN ospos_sales_items AS sales_items
		    ON sales_items.sale_id = sales.sale_id
		WHERE sales.customer_id = -1 /* claro que el id antes de crear aun aqui es -1 */
		GROUP BY sale_id
	    ) 
-- se verifica que no tenga pagos relacionados con esa venta temporal
SELECT SUM(sales_payments.payment_amount) AS total, MIN(sales_payments.payment_amount) AS min, MAX(sales_payments.payment_amount) AS max, AVG(sales_payments.payment_amount) AS average, ROUND(AVG(sales_items_temp.avg_discount), 2) AS avg_discount, ROUND(SUM(sales_items_temp.quantity), 0) AS quantity
FROM `ospos_sales`
JOIN `ospos_sales_payments` AS `sales_payments` ON `ospos_sales`.`sale_id` = `sales_payments`.`sale_id`
JOIN `ospos_sales_items_temp` AS `sales_items_temp` ON `ospos_sales`.`sale_id` = `sales_items_temp`.`sale_id`
WHERE `ospos_sales`.`customer_id` = -1 /* claro que el id antes de crear aun aqui es -1 */
AND `ospos_sales`.`sale_status` =0
GROUP BY `ospos_sales`.`customer_id` 
-- una ves investigado todo relacionado a ventas se borra lo temporal, el objeto esta en ram
DROP TEMPORARY TABLE IF EXISTS ospos_sales_items_temp
-- verifica que el correo sea unico
SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_people`.`email` = 'correo'
AND `ospos_customers`.`deleted` =0 

customer create

Los querys que se ejecutan finalmente para despues volver a la pantalla de cleintes listados:

Datos empleados se recuerda los datos empleados en la pantalla de customer o cliente:

  • Registration consent: check
  • First Name: nombre
  • Last Name: apellido
  • Gender: M
  • Email: "correo"
  • Phone Number: 5555557
  • Address 1: direccion1
  • Address 2: direccion2
  • City: ciudad
  • State: estado
  • Country: "repu domin"
  • Comments: comentario
  • Discount: 1 (%)
-- se ingresa la data segun lo que se ingreso en la pantalla de customer
INSERT INTO 
    `ospos_people` 
        (`first_name`, `last_name`, `gender`, `email`, `phone_number`, 
        `address_1`, `address_2`, `city`, `state`, `zip`, `country`, `comments`) 
    VALUES 
        ('Nombre', 'Apellido', '1', 'correo', '5555557', 
        'direccion1', 'cireccion2', 'ciudad', 'estado', '1234', 'repu domin', 'comentario') 
-- IMPORTANTISIMO: tanto el employee como el customer son "person"s
-- por ende la data de cualqueira de estos se guarda en tabla personas
-- despues segun verifica, aqui por ser customer se verifica antes insertar customer:
SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = -1 /* el id -1 es el usado antes de estar creado, este sql no se entiende */
-- insercion en tabla customer si fuera employee seria en la tabla employee
INSERT INTO 
    `ospos_customers` 
        (`consent`, `account_number`, `company_name`, `discount_percent`, `package_id`, 
        `taxable`, `date`, `employee_id`, `sales_tax_code`, `person_id`) 
    VALUES 
        (1, NULL, NULL, '1', NULL, 
        1, '2018-05-28 09:03:59', '1', '', 2) 
-- se apoya en uan tabla pra ver las ventas de el cliente recien creado (si esta en modo venta)
CREATE TEMPORARY TABLE IF NOT EXISTS ospos_sales_items_temp (INDEX(sale_id))
	    (
		SELECT
		    sales.sale_id AS sale_id,
		    AVG(sales_items.discount_percent) AS avg_discount,
		    SUM(sales_items.quantity_purchased) AS quantity
		FROM ospos_sales AS sales
		INNER JOIN ospos_sales_items AS sales_items
		    ON sales_items.sale_id = sales.sale_id
		WHERE sales.customer_id = '2'
		GROUP BY sale_id
	    ) 
-- busca las ventas del cleinte recien creado, id = 2
SELECT SUM(sales_payments.payment_amount) AS total, MIN(sales_payments.payment_amount) AS min, MAX(sales_payments.payment_amount) AS max, AVG(sales_payments.payment_amount) AS average, ROUND(AVG(sales_items_temp.avg_discount), 2) AS avg_discount, ROUND(SUM(sales_items_temp.quantity), 0) AS quantity
FROM `ospos_sales`
JOIN `ospos_sales_payments` AS `sales_payments` ON `ospos_sales`.`sale_id` = `sales_payments`.`sale_id`
JOIN `ospos_sales_items_temp` AS `sales_items_temp` ON `ospos_sales`.`sale_id` = `sales_items_temp`.`sale_id`
WHERE `ospos_sales`.`customer_id` = '2'
AND `ospos_sales`.`sale_status` =0
GROUP BY `ospos_sales`.`customer_id` 
-- borra la tabla que se uso para relacionar si hay ventas
DROP TEMPORARY TABLE IF EXISTS ospos_sales_items_temp 

IMPORTANTE no se entiende para que esos querys despues de crear el customer si es nuevo no tiene ventas.

pantalla customer

El archivo log sql completo y la pantalla customer empleada para esta explicacion:

querylog-crearcliente.sql

ospos-customer1-example

4 Consultas de Ventas

La venta es un proceso enteramente realizado via programacion php, y solo se emplea la base de datos para retraer datos, solo al final es que se realiza la insercion de la venta.

La venta es realilzada y retenida siemrpe en el objeto sesion, esto aunque no es optimo es algo que se heredo desde los inicios del desarrollo porque el equipo actual no diseño esto.

Una vez completada todos los datos del objeto venta en sesion se organizan empleando la libreria Sale_lib y alli es que se usa el modelo y se guarda en base de datos la venta.

4.1 Consultas de venta realizada: Venta en efectivo un solo pago

Este caso especifico es el mas simple, una venta con un item y un solo pago en efectivo:

Datos empleados

  • items/productos: 1
    • item 1: id=1
      • catidad 1,
      • precio: 15,
      • descuento: 1%,
      • nombre: "item1" id = 1
  • cleinte/customer: 1
    • id=2, "Nombre Apellido"
  • pagos/payments: 1
    • payment 1: cash
      • monto: 15 ($)

El log sql completo esta aqui, se explica en este documento lo mas relevante:

querylog-venta-sale1.sql

add item

Como todo el proceso se realiza sobre un objeto sesion el item se agrega a un objeto especial de venta cargado en el objeto sesion del empleado, por lo que los querys respectivos son solo de busqueda, el item usado es de id = 1.

NOTA: no es muy optimo usar el objeto sesion pero es algo que se heredo del sistema original.


SELECT * FROM `ospos_items`
WHERE   (
`ospos_items`.`item_number` = '1' /* err no nada que ver con id,*/
/* itemnumber es el codigo item para el empleado, id es para la db caual aqui son iguales */
OR `ospos_items`.`item_id` = 1
 )
AND `ospos_items`.`deleted` =0 /* ojo los items no desaparecen solo se marcan como que ya no se venden usan o tienen */
 LIMIT 1 

SELECT * FROM `ospos_items_taxes`
WHERE `item_id` = '1' 

-- VERIFICACIONES DE DISPONIBILIDAD, el item id=1 tenia locacion id=1 por eso el sig query

SELECT * FROM `ospos_stock_locations`
WHERE `location_id` = '1' 

SELECT * FROM `ospos_item_quantities`
WHERE `item_id` = '1' AND `location_id` = '1' 

SELECT * 
FROM `ospos_dinner_tables` /* y disponibilidad en lla feature de restaurante */
WHERE `status` =0 AND `deleted` =0 

SELECT *
FROM `ospos_items_taxes`
WHERE `item_id` = '1' 

add customer

Como todo el proceso se realiza sobre un objeto sesion el cliente se agrega a un objeto especial de venta cargado en el objeto sesion del empleado, por lo que los querys respectivos son solo de busqueda, el cliente usado es de id = 2 ya que como se sabe employee y customer son realmente partes de people y sus id son unicos en dicha tabla, ya que el id 1 es un employee por eso el id 2 es el cliente.

NOTA: no es muy optimo usar el objeto sesion pero es algo que se heredo del sistema original.


-- CONSULTA que ejecuta la llamada AJAX desded el input de CUSTOMERS al escribir letra "a":

SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_customers`.`person_id` = `ospos_people`.`person_id`
WHERE   (
`first_name` LIKE '%a%' ESCAPE '!'
OR  `last_name` LIKE '%a%' ESCAPE '!'
OR  CONCAT(first_name, " ", last_name) LIKE '%a%' ESCAPE '!'
OR  `email` LIKE '%a%' ESCAPE '!'
OR  `phone_number` LIKE '%a%' ESCAPE '!'
OR  `company_name` LIKE '%a%' ESCAPE '!'
 )
AND `deleted` =0
ORDER BY `last_name` ASC 

-- DESPUES verificaciones preparadas de pagos de los customer antes de agregar, el coincidente es id = 2

CREATE TEMPORARY TABLE IF NOT EXISTS ospos_sales_items_temp (INDEX(sale_id))
	    (
		SELECT
		    sales.sale_id AS sale_id,
		    AVG(sales_items.discount_percent) AS avg_discount,
		    SUM(sales_items.quantity_purchased) AS quantity
		FROM ospos_sales AS sales
		INNER JOIN ospos_sales_items AS sales_items
		    ON sales_items.sale_id = sales.sale_id
		WHERE sales.customer_id = '2'
		GROUP BY sale_id
	    ) /* creo crea una tabla temporal y relaciona los items con este customer*/
    /* recordar que una vez relacionado esta venta con el cleinte, los items estan atados a dicho cleinte */

SELECT SUM(sales_payments.payment_amount) AS total, MIN(sales_payments.payment_amount) AS min, MAX(sales_payments.payment_amount) AS max, AVG(sales_payments.payment_amount) AS average, ROUND(AVG(sales_items_temp.avg_discount), 2) AS avg_discount, ROUND(SUM(sales_items_temp.quantity), 0) AS quantity
FROM `ospos_sales`
JOIN `ospos_sales_payments` AS `sales_payments` ON `ospos_sales`.`sale_id` = `sales_payments`.`sale_id`
JOIN `ospos_sales_items_temp` AS `sales_items_temp` ON `ospos_sales`.`sale_id` = `sales_items_temp`.`sale_id`
WHERE `ospos_sales`.`customer_id` = '2' /* el seleccionado tiene id 2 segun lo que se escogio del input customers */
AND `ospos_sales`.`sale_status` =0
GROUP BY `ospos_sales`.`customer_id` 

DROP TEMPORARY TABLE IF EXISTS ospos_sales_items_temp /* en el anterior saca los items y despeus la borra */

-- lo que sigue es en el objeto y en sale_lib aqui ya esta relacionado el customer con la venta

add payment

Como todo el proceso se realiza sobre un objeto sesion el pago se agrega a un objeto especial de venta cargado en el objeto sesion del empleado, por lo que los querys respectivos son solo de busqueda, la venta tiene id -1 aun aqui en este punto a menos sea una venta rescatada o pendiente, lo unico que la diferencia es el id del que vende, el employee qu es aqui id = 1 y su cliente que aqui es id =2 el resto es escogido de el input combo de pagos y el objeto sesion:

NOTA: no es muy optimo usar el objeto sesion pero es algo que se heredo del sistema original.


-- IGUAL QUE CUANDO AGREGA ITEM solo que aqui no hay llamadas ajax, mismos SQL:

CREATE TEMPORARY TABLE IF NOT EXISTS ospos_sales_items_temp (INDEX(sale_id))
	    (
		SELECT
		    sales.sale_id AS sale_id,
		    AVG(sales_items.discount_percent) AS avg_discount,
		    SUM(sales_items.quantity_purchased) AS quantity
		FROM ospos_sales AS sales
		INNER JOIN ospos_sales_items AS sales_items
		    ON sales_items.sale_id = sales.sale_id
		WHERE sales.customer_id = '2'
		GROUP BY sale_id
	    ) /* creo crea una tabla temporal y relaciona los items con este customer*/
    /* recordar que una vez relacionado esta venta con el cleinte, los items estan atados a dicho cleinte */

SELECT SUM(sales_payments.payment_amount) AS total, MIN(sales_payments.payment_amount) AS min, MAX(sales_payments.payment_amount) AS max, AVG(sales_payments.payment_amount) AS average, ROUND(AVG(sales_items_temp.avg_discount), 2) AS avg_discount, ROUND(SUM(sales_items_temp.quantity), 0) AS quantity
FROM `ospos_sales`
JOIN `ospos_sales_payments` AS `sales_payments` ON `ospos_sales`.`sale_id` = `sales_payments`.`sale_id`
JOIN `ospos_sales_items_temp` AS `sales_items_temp` ON `ospos_sales`.`sale_id` = `sales_items_temp`.`sale_id`
WHERE `ospos_sales`.`customer_id` = '2' /* el seleccionado tiene id 2 segun lo que se escogio del input customers */
AND `ospos_sales`.`sale_status` =0
GROUP BY `ospos_sales`.`customer_id` 

DROP TEMPORARY TABLE IF EXISTS ospos_sales_items_temp /* en el anterior saca los items y despeus la borra */

save sale

Como todo el proceso se realiza sobre un objeto sesion todos los datos se toman de alli, un objeto especial de venta cargado en el objeto sesion del empleado actual:

NOTA: no es muy optimo usar el objeto sesion pero es algo que se heredo del sistema original.

El proceso de guardar la venta es en 3 partes, una la venta cabecera, otra los detalles y por ultimo la actualizacion de inventariado:

Datos empleados

  • items/productos: 1
    • item 1: id=1
      • catidad 1,
      • precio: 15,
      • descuento: 1%,
      • nombre: "item1" id = 1
  • cleinte/customer: 1
    • id=2, "Nombre Apellido"
  • pagos/payments: 1
    • payment 1: cash
      • monto: 15 ($)

-- el primer insert es de los items y de la relacion cleinte producto

INSERT INTO `ospos_sales` 
(`sale_time`, `customer_id`, `employee_id`, `comment`, `sale_status`, `invoice_number`, `quote_number`, `work_order_number`, `dinner_table_id`, `sale_type`, `exinput1`, `exinput5`) 
VALUES 
('2018-05-28 09:37:04', '2', '1', '', 0, NULL, NULL, NULL, NULL, 0, '0', '') 
/* OJO extinput1 y extinput5 son del extension fiscal impresion local propia de osposweb */
/* NOTA no hay aqui sale id, usa la hora y fechapara relacionar con tabal transaciones mas abajo */

-- el segundo insert es de los pagos, este no tiene uno de credito, sino uno total unicamente

INSERT INTO `ospos_sales_payments` 
(`sale_id`, `payment_type`, `payment_amount`) 
VALUES 
(1, 'Cash', 15.3) 
/* OJO aqui payment_type se guarda literalmente en el idioma del locale seleccionado, nunca mezcla locales  */

-- si tiene un customer asociado o se especifico el cliente, se verifica antes de el detalle de venta

SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = '2' 

SELECT `ospos_items`.*, `ospos_suppliers`.`company_name`
FROM `ospos_items`
LEFT JOIN `ospos_suppliers` ON `ospos_suppliers`.`person_id` = `ospos_items`.`supplier_id`
WHERE `item_id` = '1' 
/* OJO este segudo query se repite cuantos items existan en la venta relacionada */

-- inserta el detalle de la venta, los items de esta venta, aqui solo un producto vendido para esta venta

INSERT INTO `ospos_sales_items` 
(`sale_id`, `item_id`, `line`, `description`, `serialnumber`, `quantity_purchased`, `discount_percent`, `item_cost_price`, `item_unit_price`, `item_location`, `print_option`) 
VALUES 
(1, '1', 1, '', '', 1, '1.00', '10.00', '15.00', '1', 0) 
/* OJO se repite cuantos items existan en la venta relacionada */

-- otra vez actualizaciones de inventario (creo)

SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = '1'
AND `location_id` = '1' 

SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = '1'
AND `location_id` = '1' 
/* OJO ESTOS DOS se repite cuantos items existan en la venta relacionada */

-- AQUI REBAJA EL INVENTARIO COMO VEZ HABIA 100 (yo meti 100, y se rebajo a 99 vendio uno

UPDATE `ospos_item_quantities` SET `quantity` = 99, `item_id` = '1', `location_id` = '1'
WHERE `item_id` = '1'
AND `location_id` = '1' 
/* OJO se repite pro cada query de los dos arriba */

-- esto seria el equivalente a nuestra tabla movimientos pero movimiento para detal no mayor o despachos

INSERT INTO `ospos_inventory` 
(`trans_date`, `trans_items`, `trans_user`, `trans_location`, `trans_comment`, `trans_inventory`) 
VALUES 
('2018-05-28 09:37:04', '1', '1', '1', 'POS 1', -1) 
/* OJO el trans date el el campo relacion con la venta en el primer insert tabla sales*/

-- ahora actualizaciones de impuestos y cargos por esta venta:

/* todo este combo se repite segun la cantidad de items en la venta*/
SELECT *
FROM `ospos_items_taxes`
WHERE `item_id` = '1' 

/* este item id=1 tenia dos taxes configurados, por ende ejecutara dos inserts */
INSERT INTO `ospos_sales_items_taxes` 
    (`sale_id`, `item_id`, `line`, `name`, `percent`, `tax_type`, `rounding_code`, `cascade_tax`, `cascade_sequence`, `item_tax_amount`) 
VALUES 
    (1, '1', 1, '', '1.000', 1, 1, 0, 0, 0.15) 
INSERT INTO `ospos_sales_items_taxes` 
    (`sale_id`, `item_id`, `line`, `name`, `percent`, `tax_type`, `rounding_code`, `cascade_tax`, `cascade_sequence`, `item_tax_amount`) 
VALUES 
    (1, '1', 1, '', '2.000', 1, 1, 0, 0, 0.3) 

/* igual aqui segun los taxes de el item*/
INSERT INTO `ospos_sales_taxes` 
    (`sale_id`, `tax_type`, `tax_group`, `sale_tax_basis`, `sale_tax_amount`, `print_sequence`, `name`, `tax_rate`, `sales_tax_code`, `rounding_code`) 
VALUES 
    (1, 1, '1% ', '14.8500', 0.15, 0, '', '1.000', '', 1) 
INSERT INTO `ospos_sales_taxes` 
    (`sale_id`, `tax_type`, `tax_group`, `sale_tax_basis`, `sale_tax_amount`, `print_sequence`, `name`, `tax_rate`, `sales_tax_code`, `rounding_code`) 
VALUES 
    (1, 1, '2% ', '14.8500', 0.3, 1, '', '2.000', '', 1) 

/* enlaza con la feature de restaurant */
UPDATE `ospos_dinner_tables` SET `status` = 0 WHERE `dinner_table_id` IS NULL 

impresion venta

La venta es el detalle de cuantos productos se llevo el cliente:

/*  no usa todos los campos en la impresion sino para la logica en php */
SELECT 
    `sales_items`.`sale_id`, 
    `sales_items`.`item_id`, 
    `sales_items`.`description`, 
    `serialnumber`, 
    `line`, 
    `quantity_purchased`, 
    `item_cost_price`, 
    `item_unit_price`, 
    `discount_percent`, 
    `item_location`, 
    `print_option`, 
    `items`.
    `name` as `name`, 
    `category`, 
    `item_type`, 
    `stock_type`
FROM `ospos_sales_items` as `sales_items`
JOIN `ospos_items` as `items` 
    ON `sales_items`.`item_id` = `items`.`item_id`
WHERE `sales_items`.`sale_id` = '1'
ORDER BY `line` ASC 

ospos-venta1-example

4.2 Consultas de venta realizada: Venta dos items y dos pagos

Este caso especifico varia con dos pagos y dos items de productos y un cleitne relacionado:

Datos empleados

  • items/productos: 2
    • item 1: id=1
      • cantidad 1,
      • precio: 15,
      • descuento: 1%,
      • nombre: "item1" id = 1
    • item 2: id=2
      • cantidad 1,
      • precio: 21,
      • descuento: 1%,
      • nombre: "item2" id = 2
  • cleinte/customer: 1
    • id=2, "Nombre Apellido"
  • pagos/payments: 2
    • payment 1: cash
      • monto: 20 ($)
    • payment 2: cash
      • monto: 16.75 ($)

El log sql completo esta aqui, se explica en este documento lo mas relevante:

ospos-5-DEVEL-2-procs-min-sqls-xx-venta-id2-sql.sql

ventana de venta

ospos-5-DEVEL-2-procs-min-sqls-xx-venta-id2-presave.png

add items


/* ************* AGREGAR ITEMS item 1 */

-- SE ESCRIBIO 1 EN EL INPUT DE CODIGO DE ITEM A BUSCAR:
SELECT `item_id`, `name`, `name`
FROM `ospos_items`
WHERE `deleted` =0 AND `item_type` IN(0, 2) AND  `name` LIKE '%1%' ESCAPE '!'
ORDER BY `name` ASC 
SELECT `item_id`, `item_number`, `name`
FROM `ospos_items`
WHERE `deleted` =0 AND `item_type` IN(0, 2) AND  `item_number` LIKE '%1%' ESCAPE '!'
ORDER BY `item_number` ASC 
SELECT *
FROM `ospos_item_kits`
WHERE `name` LIKE '%1%' ESCAPE '!'
ORDER BY `name` ASC 
-- PARA MOSTRAR AL EMPLOYEE LO ENCONTRADO por este codigo item en el input box select
SELECT *
FROM `ospos_items`
WHERE   ( `ospos_items`.`item_number` = '1' OR `ospos_items`.`item_id` = 1  ) AND `ospos_items`.`deleted` =0
LIMIT 1 
SELECT *
FROM `ospos_stock_locations`
WHERE `location_id` = '1'  /* porque el item encontrado se relaciona a este location */
-- disponibilidad de este, una vez enocntrado el item id 1 filtra exacto:
SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = '1' AND `location_id` = '1' 

/* ************* AGREGAR ITEMS item 1 FIN */

-- AQUI YA AGREGO EL ITEM ID pero ahora actualiza la venta verificando los taxes, hay un solo item por ahora
SELECT * FROM `ospos_items_taxes` WHERE `item_id` = '1' 

/* ************* AGREGAR ITEMS item 2 INI */

-- SE ESCRIBIO 2 EN EL INPUT DE CODIGO DE ITEM A BUSCAR
SELECT `item_id`, `name`, `name`
FROM `ospos_items`
WHERE `deleted` =0 AND `item_type` IN(0, 2) AND  `name` LIKE '%2%' ESCAPE '!'
ORDER BY `name` ASC 
SELECT `item_id`, `item_number`, `name`
FROM `ospos_items`
WHERE `deleted` =0 AND `item_type` IN(0, 2) AND  `item_number` LIKE '%2%' ESCAPE '!'
ORDER BY `item_number` ASC 
SELECT *
FROM `ospos_item_kits`
WHERE `name` LIKE '%2%' ESCAPE '!'
ORDER BY `name` ASC 
-- PARA MOSTRAR AL EMPLOYEE LO ENCONTRADO por este codigo item
SELECT *
FROM `ospos_items`
WHERE   ( `ospos_items`.`item_number` = '2' OR `ospos_items`.`item_id` = 2  ) AND `ospos_items`.`deleted` =0
LIMIT 1 
SELECT *
FROM `ospos_stock_locations`
WHERE `location_id` = '1' /* porque el item id=2 tambien esta en location id=1 */
-- disponibilidad de este, una vez enocntrado el item id 2 filtra exacto:
SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = '2' AND `location_id` = '1' 

/* ************* AGREGAR ITEMS item 2 INI */

-- AQUI YA AGREGO EL ITEM ID pero ahora actualiza la venta verificando los taxes, hay ahora dos items
SELECT * FROM `ospos_items_taxes` WHERE `item_id` = '1' 
SELECT * FROM `ospos_items_taxes` WHERE `item_id` = '2' 

customer add


-- AQUI ESCRIBE "a" PARA BUSCAR UN CUSTOMER O UN CLIENTE

SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_customers`.`person_id` = `ospos_people`.`person_id`
WHERE   (
`first_name` LIKE '%a%' ESCAPE '!'
OR  `last_name` LIKE '%a%' ESCAPE '!'
OR  CONCAT(first_name, " ", last_name) LIKE '%a%' ESCAPE '!'
OR  `email` LIKE '%a%' ESCAPE '!'
OR  `phone_number` LIKE '%a%' ESCAPE '!'
OR  `company_name` LIKE '%a%' ESCAPE '!'
 )
AND `deleted` =0
ORDER BY `last_name` ASC 

-- AQUI YA LO MUESTRA EN EL INPUT BOX para que lo seleccione y relacione a la venta
-- CUANDO LO RELACIONA BUSCA SI ESTE TIENE VENTAS PENDIENTE DESPUES RELACIONA LOS ITEMS
-- LAS RELACIONES DE ITEM O DE PAGOS (puede que antes de relacionra cleinte ya tenga pagos)
-- SE REALIZAN EMPLEANDO TABLAS TEMPORALES de los items y de los pagos:

SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = '2' 

-- crea uan tabla temporal de los items que ya estan en la venta
CREATE TEMPORARY TABLE IF NOT EXISTS ospos_sales_items_temp (INDEX(sale_id))
	    (
		SELECT
		    sales.sale_id AS sale_id,
		    AVG(sales_items.discount_percent) AS avg_discount,
		    SUM(sales_items.quantity_purchased) AS quantity
		FROM ospos_sales AS sales
		INNER JOIN ospos_sales_items AS sales_items
		    ON sales_items.sale_id = sales.sale_id
		WHERE sales.customer_id = '2'
		GROUP BY sale_id
	    ) 
-- despues crea una tabla temporal de los pagos que ya estan en la venta 
SELECT SUM(sales_payments.payment_amount) AS total, MIN(sales_payments.payment_amount) AS min, MAX(sales_payments.payment_amount) AS max, AVG(sales_payments.payment_amount) AS average, ROUND(AVG(sales_items_temp.avg_discount), 2) AS avg_discount, ROUND(SUM(sales_items_temp.quantity), 0) AS quantity
FROM `ospos_sales`
JOIN `ospos_sales_payments` AS `sales_payments` ON `ospos_sales`.`sale_id` = `sales_payments`.`sale_id`
JOIN `ospos_sales_items_temp` AS `sales_items_temp` ON `ospos_sales`.`sale_id` = `sales_items_temp`.`sale_id`
WHERE `ospos_sales`.`customer_id` = '2' AND `ospos_sales`.`sale_status` =0
GROUP BY `ospos_sales`.`customer_id` 
-- borra todo esto
DROP TEMPORARY TABLE IF EXISTS ospos_sales_items_temp 
-- la relacion de estos se realiza despues en el objeto sesion ya que la venta esta alli antes de insertarse

-- verifica este cleinte uan vez realacionado y busc los datoa apra presentar en pantalla
SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = '2' 

payment add

no se realizan mas que verificaciones si hay respecto uan venta suspendida con el mismo cliente

sale save


 /* ********* INI savado de la venta actual con dos pagos dos items un cliente ********* */

SELECT *
FROM `ospos_customers`
JOIN `ospos_people` ON `ospos_people`.`person_id` = `ospos_customers`.`person_id`
WHERE `ospos_customers`.`person_id` = '2' 

-- ******* MANEJO DE PAGOS Y CABECERA DE LA VENTA SALVAR: ********

INSERT INTO `ospos_sales` 
    (`sale_time`, `customer_id`, `employee_id`, `comment`, `sale_status`, `invoice_number`, `quote_number`, `work_order_number`, `dinner_table_id`, `sale_type`, `exinput1`, `exinput5`) 
VALUES 
    ('2018-05-28 16:14:42', '2', '1', '', 0, NULL, NULL, NULL, NULL, 0, '0', '') 

INSERT INTO `ospos_sales_payments` 
    (`sale_id`, `payment_type`, `payment_amount`) VALUES (2, 'Cash', 20) 

INSERT INTO `ospos_sales_payments` 
    (`sale_id`, `payment_type`, `payment_amount`) VALUES (2, 'Debit Card', 16.71) 

-- ******* MANEJO DE ITEMS ************

-- VAMOS CON EL ITEM 1 AHORA:
SELECT `ospos_items`.*, `ospos_suppliers`.`company_name`
FROM `ospos_items`
LEFT JOIN `ospos_suppliers` ON `ospos_suppliers`.`person_id` = `ospos_items`.`supplier_id`
WHERE `item_id` = '1' 

-- inserta el item primero el de id=1 en el detalle de la venta:
INSERT INTO `ospos_sales_items` 
    (`sale_id`, `item_id`, `line`, `description`, `serialnumber`, `quantity_purchased`, `discount_percent`, `item_cost_price`, `item_unit_price`, `item_location`, `print_option`) 
VALUES 
    (2, '1', 1, '', '', 1, '1.00', '10.00', '15.00', '1', 0) 

-- manejo de existencia e inventario para el item primero de id=1:
SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = '1'
AND `location_id` = '1'  /* de aqui obtiene el 99 y lo rebajara a 98 para el item id=1 */

-- actualiza el inventario para el primer item el de id=1:
UPDATE `ospos_item_quantities` 
SET `quantity` = 98, `item_id` = '1', `location_id` = '1'
WHERE `item_id` = '1'AND `location_id` = '1' 

-- procede a rebajar el inventario pro transaccion:
INSERT INTO `ospos_inventory` 
    (`trans_date`, `trans_items`, `trans_user`, `trans_location`, `trans_comment`, `trans_inventory`) 
VALUES 
    ('2018-05-28 16:14:42', '1', '1', '1', 'POS 2', -1) 

-- buscamos el taxes para el primer item el de id = 1 en esta venta ya insertada:
SELECT * FROM `ospos_items_taxes` WHERE `item_id` = '1' 
-- se encontro 2 taxes para el item 1 que estaba en esta venta con id = 1
INSERT INTO `ospos_sales_items_taxes` 
    (`sale_id`, `item_id`, `line`, `name`, `percent`, `tax_type`, `rounding_code`, `cascade_tax`, `cascade_sequence`, `item_tax_amount`) 
VALUES (2, '1', 1, '', '1.000', 1, 1, 0, 0, 0.15) 
INSERT INTO `ospos_sales_items_taxes` 
    (`sale_id`, `item_id`, `line`, `name`, `percent`, `tax_type`, `rounding_code`, `cascade_tax`, `cascade_sequence`, `item_tax_amount`) 
VALUES (2, '1', 1, '', '2.000', 1, 1, 0, 0, 0.3) 

-- VAMOS CON EL ITEM 2 AHORA:
SELECT `ospos_items`.*, `ospos_suppliers`.`company_name`
FROM `ospos_items`
LEFT JOIN `ospos_suppliers` ON `ospos_suppliers`.`person_id` = `ospos_items`.`supplier_id`
WHERE `item_id` = '2' 

-- inserta el item segundo el de id=2 en el detalle de la venta:
INSERT INTO `ospos_sales_items` 
    (`sale_id`, `item_id`, `line`, `description`, `serialnumber`, `quantity_purchased`, `discount_percent`, `item_cost_price`, `item_unit_price`, `item_location`, `print_option`) 
VALUES 
    (2, '2', 2, '', '', 1, '1.00', '13.00', '21.00', '1', 0) 

-- manejo de existencia e inventario para el item segundo de id=2: de aqui obtiene 200
SELECT *
FROM `ospos_item_quantities`
WHERE `item_id` = '2' AND `location_id` = '1' 

-- actualiza el inventario pero para el segundo item el de id=2 que habia 200 y ahora 199
UPDATE `ospos_item_quantities` 
SET `quantity` = 199, `item_id` = '2', `location_id` = '1'
WHERE `item_id` = '2' AND `location_id` = '1' 

-- procede a rebajar el inventario pro transaccion del id item 2:
INSERT INTO `ospos_inventory` 
    (`trans_date`, `trans_items`, `trans_user`, `trans_location`, `trans_comment`, `trans_inventory`) 
VALUES 
    ('2018-05-28 16:14:42', '2', '1', '1', 'POS 2', -1) 

-- buscamos el taxes para el segundo item el de id = 2 en esta venta ya insertada:
SELECT * FROM `ospos_items_taxes` WHERE `item_id` = '2' 
-- se encontro 2 taxes para el item 2 que estaba en esta venta con id = 2 pero este solo una es de porcentaje:
INSERT INTO `ospos_sales_items_taxes` 
    (`sale_id`, `item_id`, `line`, `name`, `percent`, `tax_type`, `rounding_code`, `cascade_tax`, `cascade_sequence`, `item_tax_amount`) 
VALUES 
    (2, '2', 2, '', '3.000', 1, 1, 0, 0, 0.62) 
INSERT INTO `ospos_sales_taxes` 
    (`sale_id`, `tax_type`, `tax_group`, `sale_tax_basis`, `sale_tax_amount`, `print_sequence`, `name`, `tax_rate`, `sales_tax_code`, `rounding_code`) 
VALUES 
    (2, 1, '1% ', '14.8500', 0.15, 0, '', '1.000', '', 1) 
INSERT INTO `ospos_sales_taxes` 
    (`sale_id`, `tax_type`, `tax_group`, `sale_tax_basis`, `sale_tax_amount`, `print_sequence`, `name`, `tax_rate`, `sales_tax_code`, `rounding_code`) 
VALUES 
    (2, 1, '3% ', '20.7900', 0.62, 0, '', '3.000', '', 1) 
INSERT INTO `ospos_sales_taxes` 
    (`sale_id`, `tax_type`, `tax_group`, `sale_tax_basis`, `sale_tax_amount`, `print_sequence`, `name`, `tax_rate`, `sales_tax_code`, `rounding_code`) 
VALUES 
    (2, 1, '2% ', '14.8500', 0.3, 1, '', '2.000', '', 1) 

-- ******* FIN MANEJO DE ITEMS ************

-- si es con restaurante actualiza las tablas o mesas afectadas:
UPDATE `ospos_dinner_tables` SET `status` = 0 WHERE `dinner_table_id` IS NULL 

 /* ******** FIN DE INSERCION Y SALVADO DE VENTA ACTUAL ********* */

Factura resultante:

ospos-5-DEVEL-2-procs-min-sqls-xx-venta-id2-presave.png

poendiente resolver el bgug que no meustra la opcion sales to credit