/*
#
# Script demo Implementacion con FireBird DBMS 
#		
# Programacion Aplicada
# Universidad Nacional de Lujan
# 
# Lic. Guillermo Cherencio
#
# Modo de Ejecucion:  	isql ferreteria.gdb -u SYSDBA -p masterkey -o salida.txt
# Previamente se debe crear la base de datos ferreteria.gdb
# Se utiliza el usuario SYSDBA password masterkey, usuario por defecto en FireBird
# sino cambiar por el que corresponda.
# Este script puede re-ejecutarse
# Primero borro todos los objetos que pudieran quedar de ejecuciones anteriores
# Luego creo los objetos necesarios lograr la implementacion de esta base de datos
*/
SET ECHO ON;

/* Borro Triggers */
DROP TRIGGER trg_bidetalle;
DROP TRIGGER trg_budetalle;
DROP TRIGGER trg_bufactura;
DROP TRIGGER trg_budetalle2;
DROP TRIGGER trg_bdfactura;
/* Borro Procedures */
DROP PROCEDURE sp_precio_fac_ok;
DROP PROCEDURE sp_mov_fac;
/* Borro Exceptions */
DROP EXCEPTION ex_precio_fac_inc;
DROP EXCEPTION ex_suc_nro_inc;
/* Borro Views */
DROP VIEW tot_cli_max;
DROP VIEW tot_cli;
DROP VIEW tot_ven_max;
DROP VIEW tot_ven;
DROP VIEW vfactura;
DROP VIEW vfactura1;
DROP VIEW vdetalle;
COMMIT;
/* Borro Tablas */
DROP TABLE detalle;
DROP TABLE factura;
DROP TABLE articulo;
DROP TABLE vendedor;
DROP TABLE cliente;
DROP TABLE tipocliente;
DROP TABLE tipoiva;
DROP TABLE sucursal;
DROP TABLE login;
DROP TABLE detalle_his;
DROP TABLE factura_his;
/* Borro Generators */
DROP GENERATOR LOG_ID;
COMMIT;

/* Creo Tablas */
CREATE TABLE sucursal(idsuc NUMERIC(3,0) DEFAULT 1 NOT NULL,
nombre VARCHAR(40) NOT NULL,
PRIMARY KEY (idsuc));

ALTER TABLE sucursal ADD CONSTRAINT suc_idsuc  CHECK ( idsuc > 0 );

COMMIT;

SHOW TABLE sucursal;

CREATE TABLE tipoiva(idiva NUMERIC(1,0) DEFAULT 1 NOT NULL,
porc NUMERIC(5,2) NOT NULL,
PRIMARY KEY (idiva));

ALTER TABLE tipoiva ADD CONSTRAINT ti_idiva  CHECK ( idiva > 0 );

ALTER TABLE tipoiva ADD CONSTRAINT ti_porc  CHECK ( porc > 0 );

COMMIT;

SHOW TABLE tipoiva;

CREATE TABLE tipocliente(idtipo NUMERIC(1,0) DEFAULT 1 NOT NULL,
porc NUMERIC(5,2) NOT NULL,
PRIMARY KEY (idtipo));

ALTER TABLE tipocliente ADD CONSTRAINT tc_idtipo  CHECK ( idtipo > 0 );

ALTER TABLE tipocliente ADD CONSTRAINT tc_porc  CHECK ( porc > 0 );

COMMIT;

SHOW TABLE tipocliente;

CREATE TABLE cliente(idcli NUMERIC(5,0) DEFAULT 1 NOT NULL,
nombre VARCHAR(40) NOT NULL, 
direccion VARCHAR(40) NOT NULL,
te VARCHAR(40),
idiva NUMERIC(1,0) NOT NULL,
idtipo NUMERIC(1,0) NOT NULL,
PRIMARY KEY (idcli));

ALTER TABLE cliente ADD CONSTRAINT cli_idcli  CHECK ( idcli > 0 );

ALTER TABLE cliente ADD CONSTRAINT cli_idiva  CHECK ( idiva > 0 );

ALTER TABLE cliente ADD CONSTRAINT cli_idtipo CHECK ( idtipo > 0 );

ALTER TABLE cliente ADD CONSTRAINT fk_cli_tipoiva FOREIGN KEY (idiva) REFERENCES tipoiva; 

ALTER TABLE cliente ADD CONSTRAINT fk_cli_tipocliente FOREIGN KEY (idtipo) REFERENCES tipocliente;

COMMIT;

SHOW TABLE cliente;

CREATE TABLE vendedor(idven NUMERIC(3,0) DEFAULT 1 NOT NULL,
nombre VARCHAR(40) NOT NULL,
PRIMARY KEY (idven));

ALTER TABLE vendedor ADD CONSTRAINT ven_idven  CHECK ( idven > 0 );

COMMIT;

SHOW TABLE vendedor;

CREATE TABLE articulo(idart NUMERIC(5,0) DEFAULT 1 NOT NULL,
descripcion VARCHAR(40) NOT NULL,
precio_base NUMERIC(5,2) NOT NULL,
PRIMARY KEY (idart));

ALTER TABLE articulo ADD CONSTRAINT art_idart  CHECK ( idart > 0 );

ALTER TABLE articulo ADD CONSTRAINT art_precio  CHECK ( precio_base > 0 );

COMMIT;

SHOW TABLE articulo;

CREATE TABLE factura(idsuc NUMERIC(3,0) DEFAULT 1 NOT NULL,
	nro NUMERIC(8,0) DEFAULT 1 NOT NULL,
	fecha DATE NOT NULL,
	idven NUMERIC(3,0) DEFAULT 1 NOT NULL,
	idcli NUMERIC(5,0) DEFAULT 1 NOT NULL,
PRIMARY KEY (idsuc,nro));

ALTER TABLE factura ADD CONSTRAINT fac_idsuc  CHECK ( idsuc > 0 );

ALTER TABLE factura ADD CONSTRAINT fac_nro CHECK ( nro > 0 );

ALTER TABLE factura ADD CONSTRAINT fac_idven  CHECK ( idven > 0 );

ALTER TABLE factura ADD CONSTRAINT fac_idcli  CHECK ( idcli > 0 );

ALTER TABLE factura ADD CONSTRAINT fk_fac_ven FOREIGN KEY (idven) REFERENCES vendedor;

ALTER TABLE factura ADD CONSTRAINT fk_fac_suc FOREIGN KEY (idsuc) REFERENCES sucursal;

ALTER TABLE factura ADD CONSTRAINT fk_fac_cli FOREIGN KEY (idcli) REFERENCES cliente;

COMMIT;

SHOW TABLE factura;

CREATE TABLE detalle(idsuc NUMERIC(3,0) DEFAULT 1 NOT NULL,
	nro NUMERIC(8,0) DEFAULT 1 NOT NULL,
	idart NUMERIC(5,0) DEFAULT 1 NOT NULL,
cantidad NUMERIC(3,0) DEFAULT 1 NOT NULL,
precio_fac NUMERIC(5,2) NOT NULL,
PRIMARY KEY (idsuc,nro,idart));

ALTER TABLE detalle ADD CONSTRAINT det_idsuc  CHECK ( idsuc > 0 );

ALTER TABLE detalle ADD CONSTRAINT det_nro  CHECK ( nro > 0 );

ALTER TABLE detalle ADD CONSTRAINT det_idart  CHECK ( idart > 0 );

ALTER TABLE detalle ADD CONSTRAINT det_cantidad  CHECK ( cantidad > 0 );

ALTER TABLE detalle ADD CONSTRAINT det_precio  CHECK ( precio_fac > 0 );

ALTER TABLE detalle ADD CONSTRAINT fk_det_fac FOREIGN KEY (idsuc,nro) REFERENCES factura;

ALTER TABLE detalle ADD CONSTRAINT fk_det_art FOREIGN KEY (idart) REFERENCES articulo;

COMMIT;

SHOW TABLE detalle;

/*
Implementamos los atributos calculados utilizando Views:
Calculo subtotal en detalle de factura
*/
CREATE VIEW vdetalle (idsuc,nro,idart,cantidad,precio_fac,subtotal) AS
SELECT	idsuc,
	nro,
	idart,
	cantidad,
	precio_fac,
	(cantidad * precio_fac)
FROM detalle;

COMMIT;

SHOW VIEW vdetalle;

/*
Calculo el neto de la factura
*/
CREATE VIEW vfactura1 (nro,idsuc,fecha,idven,idcli,neto) AS
SELECT	f.nro,
	f.Idsuc,
	f.fecha,
	f.idven,
	f.idcli,
	sum(d.subtotal)
FROM	factura f, vdetalle d
WHERE	f.nro = d.nro and f.idsuc = d.idsuc
GROUP BY f.nro,f.idsuc,f.fecha,f.idven,f.idcli;

COMMIT;

SHOW VIEW vfactura1;

/*
Calculo $ iva, % iva, total de factura
*/
CREATE VIEW vfactura (nro,idsuc,fecha,idven,idcli,neto,porc,iva,total) AS
SELECT	f.nro,
	f.Idsuc,
	f.fecha,
	f.idven,
	f.idcli,
	f.neto,
	ti.porc,
	(f.neto * (ti.porc / 100)),
	(f.neto + (f.neto * (ti.porc / 100)))
FROM	vfactura1 f, cliente c, tipoiva ti
WHERE	f.idcli = c.idcli and c.idiva = ti.idiva;

COMMIT;

SHOW VIEW vfactura;

/*
Ingresamos datos de prueba:
	Teniendo en cuenta la dependencia de objetos y el concepto de integridad (ya sea tanto referencial, de dominio o de entidad) podemos hacer el ingreso de datos para "poblar" esta base de datos.  También es muy recomendable hacer un ingreso de datos con el objetivo de tratar de violar la integridad de la misma para verificar que todos los ojetos fueron creados con las restricciones y controles necesarios para mantener íntegra a la base de datos a pesar de los intentos por ingresar datos incorrectos o inconsistentes.
*/
INSERT INTO sucursal VALUES(1,'LUJAN');
COMMIT;
SELECT * FROM sucursal;
INSERT INTO tipoiva VALUES(1,10.50);
INSERT INTO tipoiva VALUES(2,21);
COMMIT;
SELECT * FROM tipoiva;
INSERT INTO tipocliente VALUES(1,10);
INSERT INTO tipocliente VALUES(2,15);
INSERT INTO tipocliente VALUES(3,20);
COMMIT;
SELECT * FROM tipocliente;
INSERT INTO cliente VALUES(1,'JOSE','MITRE 231','02323-111111',2,1);
INSERT INTO cliente VALUES(2,'ANA','SAN MARTIN 123','02323-222222',1,3);
COMMIT;
SELECT * FROM cliente;
INSERT INTO vendedor VALUES(1,'PEDRO');
INSERT INTO vendedor VALUES(2,'ANDREA');
COMMIT;
SELECT * FROM vendedor;
INSERT INTO articulo VALUES(100,'PINZA',8.50);
INSERT INTO articulo VALUES(200,'TENAZA',9.50);
INSERT INTO articulo VALUES(300,'MARTILLO',6.25);
INSERT INTO articulo VALUES(400,'MAZA',9.15);
INSERT INTO articulo VALUES(500,'PICO',15.55);
INSERT INTO articulo VALUES(600,'SERRUCHO',21.15);
COMMIT;
SELECT * FROM articulo;
INSERT INTO factura VALUES(1,10001,'01-MAR-2003',1,2);
INSERT INTO detalle VALUES(1,10001,100,2,6.8);
INSERT INTO detalle VALUES(1,10001,200,3,7.6);
INSERT INTO detalle VALUES(1,10001,300,1,5);
INSERT INTO detalle VALUES(1,10001,400,1,7.32);
COMMIT;
INSERT INTO factura VALUES(1,10002,'02-MAR-2003',1,1);
INSERT INTO detalle VALUES(1,10002,100,1,7.1);
INSERT INTO detalle VALUES(1,10002,200,1,8.1);
COMMIT;
INSERT INTO factura VALUES(1,10003,'today',1,1);
INSERT INTO detalle VALUES(1,10003,100,1,7.2);
INSERT INTO detalle VALUES(1,10003,200,1,8.2);
COMMIT;
SELECT * FROM factura;
SELECT * FROM detalle;

/*
Controlamos que el precio facturado no sea menor al precio base del mismo menos el descuento de acuerdo al tipo de cliente en cuestion:

Solucion:
Debemos controlar los eventos de insert y de update sobre la tabla detalle, debido a que
FireBird no me permite crear un mismo trigger object para controlar ambos eventos (insert y
update) al mismo tiempo, estoy obligado a crear dos trigger events, en este caso, trg_bidetalle
y trg_budetalle (trigger before insert detalle y trigger before update detalle).
La validacion de ambos triggers es identica, para no escribir ambas veces el mismo codigo, podemos
hacer reutilizacion del mismo escribiendo un procedimiento comun a ambos triggers que realice el
grueso del trabajo y nos devuelva 0 cuando el precio sea incorrecto y 1 cuando el precio sea correcto.  De paso, es una buena oportunidad para ver una aplicacion de los store procedures.
Creo Excepcion para el manejo de error de ambos triggers
Creo Procedure a ser utilizado por ambos triggers para el control de precios
Creo trigger events que hacen uso del procedimiento y de la excepcion
*/

CREATE EXCEPTION ex_precio_fac_inc 'Precio a Facturar demasiado barato';

SET TERM !! ;
CREATE PROCEDURE sp_precio_fac_ok (idsuc NUMERIC(3,0),nro NUMERIC(8,0),idart NUMERIC(5,0),precio_fac NUMERIC(5,2))
RETURNS (precio_ok NUMERIC(1,0))
AS
	DECLARE VARIABLE preciobase NUMERIC(5,2);
	DECLARE VARIABLE pordto     NUMERIC(5,2);
BEGIN
	SELECT ar.precio_base FROM articulo ar
	WHERE ar.idart = :idart
	INTO :preciobase;

	SELECT tc.porc FROM tipocliente tc,cliente cl,factura fc 
	WHERE :idsuc = fc.idsuc and :nro = fc.nro and 
                                    cl.idcli = fc.idcli and tc.idtipo = cl.idtipo 
	INTO :pordto;

	IF (:precio_fac < (:preciobase - (:preciobase * (:pordto / 100)) ) ) THEN
		precio_ok = 0;
	ELSE
		precio_ok = 1;
	EXIT;
END !!
SET TERM ; !!

SET TERM !! ;
CREATE TRIGGER trg_bidetalle FOR detalle BEFORE INSERT
AS 
	DECLARE VARIABLE precio_ok  NUMERIC(1,0);
BEGIN
	EXECUTE PROCEDURE sp_precio_fac_ok NEW.idsuc,NEW.nro,NEW.idart,NEW.precio_fac RETURNING_VALUES :precio_ok;
	IF (:precio_ok = 0) THEN
		EXCEPTION ex_precio_fac_inc; 
END !!
SET TERM ; !!


SET TERM !! ;
CREATE TRIGGER trg_budetalle FOR detalle BEFORE UPDATE
AS 
	DECLARE VARIABLE precio_ok  NUMERIC(1,0);
BEGIN
	EXECUTE PROCEDURE sp_precio_fac_ok NEW.idsuc,NEW.nro,NEW.idart,NEW.precio_fac RETURNING_VALUES :precio_ok;
	IF (:precio_ok = 0) THEN
		EXCEPTION ex_precio_fac_inc; 
END !!
SET TERM ; !!

/*
Controlamos que no se realicen cambios en la sucursal o nro de factura, tanto en facturas como en detalle

Solicion:
Crear excepcion para manejo de error, advertir la imposibilidad de modificar estas columnas
Crear trigger para controlar evento update sobre tabla factura
Crear trigger para controlar evento update sobre tabla detalle
En ambos trigger controlar las columnas sucursal y nro de factura y utilizar la excepcion
previamente creada
*/

CREATE EXCEPTION ex_suc_nro_inc 'No se puede modificar ni Sucursal ni Nro. Factura';

SET TERM !! ;
CREATE TRIGGER trg_budetalle2 FOR detalle BEFORE UPDATE
AS 
BEGIN
	IF ( NEW.idsuc <> OLD.idsuc OR NEW.nro <> OLD.nro ) THEN
		EXCEPTION ex_suc_nro_inc;
END !!
SET TERM ; !!

SET TERM !! ;
CREATE TRIGGER trg_bufactura FOR factura BEFORE UPDATE
AS 
BEGIN
	IF ( NEW.idsuc <> OLD.idsuc OR NEW.nro <> OLD.nro ) THEN
		EXCEPTION ex_suc_nro_inc;
END !!
SET TERM ; !!

/*
Controlamos que si se pretende borrar una factura, también se borre todo su detalle y quede registrado en una tabla de "login" la acción realizada:

Solucion:
Debemos crear una tabla de "login" en donde guardar la informacion de control, en esta tabla guardaremos la fecha-hora del cambio, un texto relacionado con el cambio y generaremos en forma automatica una clave numerica.  En este tipo de tablas hay que tener especial cuidado con su clave primaria para evitar que la misma se repita o sea demasiado extensa (no minima), para ello, la generacion automatica de un id por parte del SGBD suele ser la mejor opcion.
Crear trigger para controlar evento delete sobre la tabla factura y este debe borrar todas
las lineas de detalle de dicha factura antes de borrar la factura propiamente dicha, luego
guardar en la tabla de login la accion realizada
*/

CREATE GENERATOR LOG_ID;
SET GENERATOR LOG_ID TO 0;

CREATE TABLE login (
	id integer not null primary key,
	fecha timestamp not null,
	descripcion varchar(255) not null);

SET TERM !! ;
CREATE TRIGGER trg_bdfactura FOR factura BEFORE DELETE
AS 
	DECLARE VARIABLE nro_fac NUMERIC(8,0);
	DECLARE VARIABLE mensaje VARCHAR(255);
BEGIN
	IF ( OLD.idsuc IS NOT NULL AND OLD.nro IS NOT NULL ) THEN
	   BEGIN
		SELECT COUNT(*) FROM detalle
		WHERE idsuc = OLD.idsuc and nro = OLD.nro
		INTO :nro_fac;
		IF ( :nro_fac > 0 ) THEN 
		   BEGIN
			mensaje = USER || ' Borro Factura. ' || OLD.idsuc || ' ' || OLD.nro || ' con ' || nro_fac || ' lineas de detalle';
			DELETE FROM detalle WHERE idsuc = OLD.idsuc AND nro = OLD.nro;
			INSERT INTO login VALUES(GEN_ID(LOG_ID,1),'now',:mensaje);
		   END
	   END
END !!
SET TERM ; !!

/*
Resolvemos las consultas:

El vendedor que vendio mas: (o los vendedores que vendieron mas), podemos pensar esta consulta de la siguiente manera:
1. Calcular el total de venta por vendedor
2. Sacar el máximo del punto 1, para saber el monto de mayor venta
3. Realizar un  join entre 1 y 2 por igualdad en el monto de venta; de esta manera, me quedaré con la o las tuplas que tengan el mayor monto. 
4. Realizar join entre tabla vendedor y 1 por igualdad de vendedor
5. Mostrar el atributo nombre de la tabla vendedor

Calcular el total de venta por vendedor
*/
CREATE VIEW tot_ven (idven,tven) AS 
SELECT 	F.IDVEN,
	SUM(F.TOTAL)
FROM	VFACTURA F
GROUP BY F.IDVEN;
COMMIT;

SHOW VIEW tot_ven;

/*
Sacar el máximo de la consulta anterior
*/
CREATE VIEW tot_ven_max (tmax) AS 
SELECT 	MAX(TVEN)
FROM	tot_ven;
COMMIT;

SHOW VIEW tot_ven_max;

/*
Realizo los distintos joins y muestro nombre del vendedor de mayor venta:
*/
SELECT 	vv.nombre
FROM	vendedor vv, tot_ven tv, tot_ven_max tvm
WHERE	tvm.tmax = tv.tven and
	vv.idven = tv.idven;

/*
El cliente que mas nos compro: (o bien, el o los clientes a los cuales le vendimos mas), podemos pensar esta consulta de la siguiente manera:
1. Calcular el total de venta por cliente
2. Sacar el máximo del punto 1, para saber el monto de mayor venta
3. Realizar un  join entre 1 y 2 por igualdad en el monto de venta; de esta manera, me quedaré con la o las tuplas que tengan el mayor monto. 
4. Realizar join entre tabla cliente  y 1 por igualdad de vendedor
5. Mostrar el atributo nombre de la tabla cliente


Calcular el total de venta por cliente
*/
CREATE VIEW tot_cli (idcli,tven) AS 
SELECT 	F.IDCLI,
	SUM(F.TOTAL)
FROM	VFACTURA F
GROUP BY F.IDCLI;
COMMIT;

SHOW VIEW tot_cli;

/*
Sacar el máximo de la consulta anterior
*/
CREATE VIEW tot_cli_max (tmax) AS 
SELECT 	MAX(TVEN)
FROM	tot_cli;
COMMIT;

SHOW VIEW tot_cli_max;

/*
Realizo los distintos joins y muestro nombre del cliente de mayor venta:
*/
SELECT 	cc.nombre
FROM	cliente cc, tot_cli tc, tot_cli_max tcm
WHERE	tcm.tmax = tc.tven and
	cc.idcli = tc.idcli;

/*
Total de ventas por categoría de cliente: podemos pensar esta consulta de la siguiente manera:
1. Join entre factura y cliente
2. Join entre tipocliente y 1
3. Agrupamiento por tipocliente y sumarizar por total de factura 
*/
SELECT 	TC.IDTIPO,
	SUM(F.TOTAL) TOTALTIPO 
FROM	VFACTURA F, CLIENTE C, TIPOCLIENTE TC
WHERE	F.IDCLI = C.IDCLI AND TC.IDTIPO = C.IDTIPO
GROUP BY TC.IDTIPO;

/*
Resolvemos los procesos utilizando FireBird:


Para resolver este problema vamos a desarrollar un procedimiento que reciba como parametro de entrada la fecha tope y todas las facturas y detalle de facturas que sean menores a dicha fecha serán copiadas a tablas históricas y luego eliminadas de las tablas de trabajo.  Todo el proceso deberá realizarse en el marco de una transacción para asegurarnos que el proceso se lleve a cabo en forma completa o bien no se realice ninguna actualización y la base de datos quede en el estado anterior al proceso.

Las tablas factura_his y detalle_his no poseen las restricciones
que tienen las tablas factura y detalle, debido a que estas funcionan
como tablas historicas, auxiliares, a las cuales, seguramente todos
los usuarios tendran acceso de solo lectura, excepto el o los usuarios
que puedan actualizarla a traves de este proceso.
Ademas, por una cuestion de historicidad, estas tablas pueden ser incon-
sistentes con la tabla de productos, clientes o vendedores.
*/
CREATE TABLE factura_his(idsuc NUMERIC(3,0) NOT NULL,
	nro NUMERIC(8,0) NOT NULL,
	fecha DATE NOT NULL,
	idven NUMERIC(3,0) NOT NULL,
	idcli NUMERIC(5,0) NOT NULL,
	PRIMARY KEY (idsuc,nro));

CREATE TABLE detalle_his(idsuc NUMERIC(3,0) NOT NULL,
	nro NUMERIC(8,0) NOT NULL,
	idart NUMERIC(5,0) NOT NULL,
	cantidad NUMERIC(3,0) NOT NULL,
	precio_fac NUMERIC(5,2) NOT NULL,
	PRIMARY KEY (idsuc,nro,idart));

/*
La llamada a este procedimiento debe estar
realizada dentro de una transaccion
*/
SET TERM !! ;
CREATE PROCEDURE sp_mov_fac (fecha_hasta DATE)
RETURNS (proc_ok NUMERIC(1,0))
AS
	DECLARE VARIABLE can_fac NUMERIC;
	DECLARE VARIABLE mensaje VARCHAR(255);
BEGIN
	SELECT COUNT(*) FROM factura fa
	WHERE fa.fecha < :fecha_hasta
	INTO :can_fac;
	IF (:can_fac > 0) THEN
	   BEGIN
		/* comienzo transaccion */

			INSERT INTO factura_his
				SELECT * from factura
				WHERE fecha < :fecha_hasta;

			INSERT INTO detalle_his
				SELECT * from detalle dt
				WHERE EXISTS (
					SELECT * from factura fa
					WHERE fa.idsuc = dt.idsuc and fa.nro = dt.nro and
					      fa.fecha < :fecha_hasta );
			/* 
			   solo borro factura, el trigger trg_bdfactura
			   hara el resto del trabajo
			*/
			DELETE FROM factura 
				WHERE fecha < :fecha_hasta;
			
		/* termino transaccion */
		proc_ok = 1;
	   END
	ELSE proc_ok = 0;
	EXIT;
END !!
SET TERM ; !!

/*
Ejemplo de ejecucion de este proceso desde isql:

SQL> EXECUTE PROCEDURE sp_mov_fac 'today';

PROC_OK
=======
      1

SQL> COMMIT;
*/

SET ECHO OFF;
OUTPUT;
