eStore Database/Application
Transcription
eStore Database/Application
eStore Database/Application Final Project Report By: David Sampson and Steve Kazmierczak December 13, 2007 WPI CS542 – Professor Rundensteiner INTRODUCTION AND OVERVIEW ......................................................................................................................1 ABSTRACT .................................................................................................................................................................1 KEYWORDS................................................................................................................................................................1 HIGH LEVEL OBJECTIVES ACCOMPLISHED ..................................................................................................................1 ANALYSIS – DOMAIN REQUIREMENTS OF MODELED DATA ....................................................................1 APPLICATION REQUIREMENTS ...................................................................................................................................2 DATA MODEL TO SUPPORT BUSINESS LOGIC.............................................................................................................2 ER DIAGRAMS AND SYSTEM DESIGN: .......................................................................................................................2 Overview ..............................................................................................................................................................2 Customer Data Exploded View ............................................................................................................................3 Question: Why model street# and street_name separately? .............................................................................................. 3 Order and Product Data Exploded View .............................................................................................................4 Question: Why separate ORDER_ITEM from ORDER ? ................................................................................................ 4 Question: Why capture INVENTORY_EVENTS (stock received)? ................................................................................ 5 Revisions Since Proposal .....................................................................................................................................5 Revisions Since Proposal .....................................................................................................................................7 ARCHITECTURE AND TOOLS ..............................................................................................................................7 TOOLS USED ..............................................................................................................................................................7 Database ..............................................................................................................................................................7 Why we chose Oracle and not MySQL post proposal ..........................................................................................7 FRONT-END ...............................................................................................................................................................8 Additional Java, Swing, SQL references used: ....................................................................................................9 JDBC ........................................................................................................................................................................9 Resources on How To Connect using JDBC:.....................................................................................................10 IMPLEMENTATION DETAILS.............................................................................................................................10 ER DIAGRAMS TO SQL TABLES ..............................................................................................................................10 STORED PROCEDURES, TRIGGERS AND/OR ALGORITHMS (PSEUDOCODE) ...............................................................17 Prototype Procedure for Order Fulfillment:......................................................................................................17 Prototype Procedure for Customer Order Process: ..........................................................................................18 Prototype Procedure for Customer Order History: ...........................................................................................18 Prototype Procedure for Inventory Event:.........................................................................................................18 APPLICATION DETAILS ............................................................................................................................................18 Original Prototype Employee Application .........................................................................................................19 Original Prototype Employee Application Screen Shots (deprecated)............................................................................ 19 Customer Application as of Progress Report time: ......................................................................................................... 22 THE CUSTOMER ESTORE APPLICATION TODAY (WITH TRIGGER/PROCEDURE DISCUSSION) .24 1) SHOP FOR PRODUCTS:..........................................................................................................................................24 2) VIEW CART MODE ...............................................................................................................................................25 3) CHECKOUT (3 STEPS)...........................................................................................................................................25 3.1) Select Shipping Address or Cancel Checkout .............................................................................................25 3.2) Select Billing Address or Cancel Checkout ................................................................................................26 3.3) Confirm Order and Submit .........................................................................................................................26 4) ACCOUNT MANAGEMENT ....................................................................................................................................27 ESTORE ORDER FULFILLMENT THEORY...................................................................................................................28 EXAMPLE ORDER @ SUBMIT ORDER TIME ..............................................................................................................28 What Happens In Oracle When Order Is Submitted ..........................................................................................29 Case 2 (order_item could not ship, send notification):......................................................................................31 Additional profit costing example (multiple inventory_events per order_item): ...............................................36 QUERIES ..................................................................................................................................................................37 EMPLOYEE APPLICATION AND ESTORE STATISTICS .................................................................................................37 EMPLOYEE APP STATISTICS VIEW ...........................................................................................................................40 Product Stats .......................................................................................................................40 Inventory Stats ......................................................................................................................41 Order Fulfillment Stats Profit Stats ..................................................................................................43 ................................................................................................................................44 Unfulfilled Order Stats ...................................................................................................46 TESTING ..................................................................................................................................................................46 ACCOMPLISHMENTS & COMMENTS ..............................................................................................................46 SCHEDULE ...............................................................................................................................................................47 Goals Completed Since Project Progress Report: ........................................................................................................... 47 Table Of Contents 2 of 2 Introduction and Overview Abstract Our project simulates eStore, an on-line merchant via a database with respective front-end applications. We model the process of customers purchasing products via a shopping cart model, tracking transactions yielding product and sales metrics. The process of conducting a transaction and stocking inventory are areas for extension of the project focus. Our target user group includes anyone familiar with eCommerce and buying products via the Internet, people interested in databases and database applications, and anyone attracted to portable applications utilized via the Internet. The exercise of constructing a database architecture involved planning and revision of an Entity Relationship model to support desired functionality and have reasonable future extensibility if desired. Keywords • Database, eCommerce, Java, JDBC, SQL, Oracle, Swing, ER Diagrams High level objectives accomplished This presents some of the following challenges accomplished: • Represent the data in terms of entities and relationships. • Translate ER model into a physical schema in Oracle using SQL, and enforcing integrity constraints using sequences, triggers, and client application data checks. • Represent business logic partially using triggers and stored procedures • Implement Java front-end for interaction with the Oracle DB. • Use JDBC so the Java application can talk to and write to the DB. • Design a prototype user interface to facilitate corporate data entry. • Implemented cart flow and order events in the GUI/DB. • Implemented existing plan for shopping cart and order fulfillment in DB. • Construct procedures for updating and ordering inventory in DB and GUI, shipping products, running queries, and deriving calculated attribute values in DB. • Implementing sample data and queries to illustrate value from using our DB business model. • query output via graphical charts, additional useful features in shopping cart, checkout process, and further utilize vendor information in the DB schema. • For future work: storing pictures (BLOBs) in Oracle DB Analysis – Domain Requirements of Modeled Data Analysis of the domain required for the data began with a discussion about what type of entities we wanted to model for eStore. eStore sells merchandise in different categories (ex books, electronics, and music). Customers buy these products via shopping cart style order process, and the business tracks who bought what, when and for what price, as well as the wholesale costs for stocking inventory. These entities required meaningful attributes that would help in associating them with respective relationships to form the basis for business logic. A brain storming session via whiteboard took place on October 2, 2007 where we constructed a mock Entity Relationship (ER) diagram of entities, attributes of those entities, participating PAGE 1 OF 48 relationships and associated integrity constraints. The goal here was to construct a model that would allow for useful and interesting data to be derived via queries, and be manageable when constructing triggers and procedures to operate the eStore business logic. Background material for the eStore concept was modeled after online retails such as Amazon.com, iTunes Store, buy.com, which utilize a shopping cart and business transaction processing system. Due to the limited time for planning and implementation we tried to focus the scope on what we find to be the most interesting areas of the business infrastructure from a DB and GUI front-end perspective. Application Requirements • • • • • • • Oracle Database with SQL, PL/SQL JRE 1.5.1 or higher JDBC connectivity to DB Java Swing (portable) front-end GUI prototype application for interacting with DB Sample product data for use by testers who conduct sample orders SMTP access via Oracle modules (recently added) Java graphing package (recently added) Data Model to Support Business Logic The database is robust enough to capture commonly used retail data, much of which is implemented in the schema, and some left for future extension: • • • • • • • • Which customers order what products, when, and for how much? How was a customer order billed and with what form of payment? How much product inventory exists at any one time for customers to purchase? What the current profit margin is for a particular product given actual costs to carry product (wholesale unit cost vs. sale price)? When, where and to whom we ship an order? Which products are selling the best in a given time period? Who manages product departments? Extensible schema/architecture for future requirements. ER Diagrams and System Design: Overview Figure 3 (pg 7) is our ER diagram for eStore illustrating entities, their relationships, and integrity constraints between these relationships, as well as areas for extension. The two primary relationship sets are 1) customer data and 2) order and product data. Attributes have been excluded from this high level overview. PAGE 2 OF 48 Customer Data Exploded View Attributes for customer related data is captured below in Figure 1: passwd f_Name l_Name email customer_id phone CUSTOMER card# exp.date type has cid has ISA SHIP_addr registered BILL_addr CCARD ADDRESS name street # street_name country addr_2 city state zip Figure 1: exploded view showing attributes • • • • • • CUSTOMERs have a customer_id, First Name, Last Name, Email address, phone number and passwd. Constraint: Email will be unique to the entity and serve as the login name. Constraint: Passwords must be more than 6 characters. SHIP_address and BILL_address are both (ISA) an ADDRESS. An address has a Name, Street#, Street Name, Address Line 2, City, State, Zip Code, and Country. Schema will implement this with primary key / foreign key relationships. Question: Why model street# and street_name separately? If population density is high within a given large city, this would allow us to track how many customers per section of street#s, or by street. For example, 1st Ave in Manhattan, NYC runs through many sections of town that cross socio-economic boundaries, business districts, income levels, etc. • • • BILL_addresses are registered to CreditCard/PaymentTypes CreditCard has a type, card#, expiration date, and cardid Constraint: Exactly one credit card is associated with a BILL_address PAGE 3 OF 48 Order and Product Data Exploded View Attributes for order and product related data is captured below in Figure 2: date_recvd picture_blob pictid product_cost qty_avail ship_cost stock_num PICTURE qty_recvd active profit INVENTORY EVENTS calc_ship_cost notified oitem_num stocked_by qty has date_shipped unit_price ORDER_ITEM name of_type description PRODUCT weight has markup% curr_profit ship_cost ORDER calc_total qty_items order_num status qty product_id curr_price date placed Figure 2: exploded view showing attributes ORDERs consist of an order_number, date_placed, status, qty_items, and calculated_total cost of order including shipping. • Each ORDER is billed_to exactly 1..1 BILL_addr, paid_with exactly 1..1 CCARD of a given CUSTOMER and shipped_to exactly 1..1 SHIP_addr. By enforcing these 1..1 constraints, we simplify the business logic for billing. • Each ORDER has 1..n ORDER_ITEMs, and each ORDER_ITEM is associated with exactly 1..1 ORDER. Each ORDER_ITEM is of_type PRODUCT, of qty. • Each ORDER_ITEM is made up of an ORDER_ITEM_number, a date_shipped, qty of product, unit_price, a derived calculated_shipping_cost based on qty x , PRODUCT.ship_cost, profit and notified (to track status of notification to customer). Question: Why separate ORDER_ITEM from ORDER ? One goal of this model is to track actual purchased PRODUCTs at sale time cost. An order is made up of various quantities of a particular PRODUCT, and each PRODUCT may or may not PAGE 4 OF 48 be in stock, and may ship from different locations. Separately, each ORDER_ITEM has derived values for calc_shipping_cost based on the qty_items of_type PRODUCT x a PRODUCT.ship_cost. • ORDER_ITEMs are exactly 1..1 of_type PRODUCTs. • Each PRODUCT contains a name, customer facing description, weight, derived current_profit, current_price, derived qty in inventory, ship_cost per unit, markup%, picture_blob (binary object) and product_id. • PRODUCTs are supplied_by VENDORs which is used for reordering a PRODUCT in eStore's inventory. • VENDORs have a venderid, contact_name, phone#, address, and preferred vendor status. • PRODUCTs are stocked_by INVENTORY_EVENTs. • INVENTORY_EVENTS consists of a stock_num, date_received, qty_received of a PRODUCT, total product_cost in a given shipment, active, qty_avail, and ship_cost of the PRODUCT qty_received. Question: Why capture INVENTORY_EVENTS (stock received)? The WAREHOUSE captures wholesale costs of inventory that is stocked, as it is shipped to eStore. When capturing a product_cost (or subtotal of costs of the products portion of the shipment), an INVENTORY_EVENTS.ship_cost for the entire delivery, and the qty_received of a product, we can derive the wholesale unit cost of each PRODUCT in the shipment, as well as the unit cost of all remaining PRODUCT quantities of all recently received INVENTORY_EVENTS for the same PRODUCT. An aggregate of INVENTORY_EVENTS for a particular PRODUCT can be used to derive a curr_price based on a desired markup% (according to a procedural formula) and the calculated wholesale unit cost including INVENTORY_EVENTS.ship_cost to arrive at a PRODUCT.curr_price and unit PRODUCT.curr_profit, as well as a PRODUCT.qty which is incremented by new INVENTORY_EVENTS, and decremented by ORDER_ITEMs being shipped. (This inventory increment and decrement over time is seen in a chart in our GUI). Revisions Since Proposal • • • • • • • EMPLOYEE section was solidified as depicted in Figure 3. EMPLOYEE entity was revised to include the attributes: eid, fname, lname, addr_id, username, passwd, phone#, emp_role. emp_role stores the role: admin or mgr of an employee. Employees have exactly one address. Removed relationship paid_with between ORDER and CCARD since an ORDER is billed_to exactly one BILL_addr and exactly one CCARD is registered to a BILL_addr. Added area for future addition if time permits: PICTURE table to store binary large objects (BLOBs) of pictures, exactly at most 1 per product. This was kept in a separate table so queries would not have to handle this special binary type when doing a select *. This is an issue for the GUI and sqlplus. PAGE 5 OF 48 Fig 1 INVENTORY_EVENT PICTURE Note: Dashed line for PICTURE and VENDOR is an area for future extension. has stocked_by VENDOR supplied_by ORDER_ITEM PRODUCT of_type DEPT catalogued has manages EMPLOYEE ORDER eid phone fname addr_id place lname emp_role ship_to username billed_to CUSTOMER password has has has Fig 2 SHIP_addr ISA BILL_addr registered CCARD ADDRESS Figure 3: Overall System Logical View (Represented as ER - Diagram) PAGE 6 OF 48 Revisions Since Proposal • • • • • • • Added active (Boolean functionality) and qty_avail (qty of inventory events remaining in stock as a result of this inventory_event) to inventory_events to help in profit calculation for order_items that span multiple inventory_events (range of input costs vs. sale price). Order_items has profit instead of unit_profit, to illustrate profit over multiple inventory_events for a particular product. Logic was also changed so that an entire order_item must ship at once (all qty of a particular ordered product within a given order). Enhancement of multiple triggers for inventory_events pre and post insert, and order_items pre-insert. Procedure to calculate profit-per-order_item SMTP functionality implemented in Oracle and utilized by stored procedure to send email notification about dynamic events specifically when an order_item cannot be fulfilled until a specific qty is re-stocked for administrative use. Chart making package added to plot data returned by inventory-over-time queries. Scores of GUI enhancments, such as the full implementation of a shopping cart, store browser, account management (including shipped vs. unshipped orders) for customers, and the ability to add inventory_events (stock the store), and get statistics (run queries) within the employee application. Architecture and Tools Tools Used • • • • • • • Oracle 10g NetBeans 5.5.1 IDE Java 1.5 JDK/JRE development platform and runtime environment JDBC Oracle classes12.zip drivers for database connectivity CVS Source Control, via sourceforge.wpi.edu Putty SSH and Cygwin X-Server for connectivity to sqlplus Scientific Graphics Toolkit (SGT) graphing package developed by the US NOAA available here: http://www.epic.noaa.gov/java/sgt/ Database • We chose Oracle 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production running on Linux with 2.6.9-55.0.9.ELsmp kernel. Why we chose Oracle and not MySQL post proposal • • • • Oracle is an enterprise level DB server platform that we can expect to get potentially better performance from. Oracle is already being administered by WPI, this permits for us to not have to perform maintenance but also limits our control. It's not free for commercial use, but for the purposes of this course a free license can be obtained if we wished to install it locally as a single user (not at WPI). Oracle is more robust for at least these reasons: PAGE 7 OF 48 1. Can enforce CHECK integrity constraints within a table to be applied during INSERT o http://techonthenet.com/oracle/check.php 2. MySQL does not have a CHECK constraint at the table level, to model integrity constraints one has to create an updateable view and use "WITH CHECK CONTRAINT" which is not the same, and is less reliable to accomplish the goal. This would also require mimicking CHECK constraints with triggers and stored procedures. o Emulating check: http://db4free.blogspot.com/2006/01/emulating-checkconstraints-with-views.html o WITH CHECK OPTION part of CREATE VIEW http://dev.mysql.com/doc/refman/5.1/en/create-view.html o Why not to use: http://forums.mysql.com/read.php?61,17513,20375#msg-20375 o http://forums.mysql.com/read.php?61,17513,18905#msg-18905 3. Additional differences between Oracle and standard SQL: http://infolab.stanford.edu/~ullman/fcdb/oracle/or-nonstandard.html#transactions • • • Challenge: There was initially a difficulty in connecting to Oracle at WPI for what was later found to be a lack of appropriate connection string. This was the single hurdle keeping us from using Oracle. Oracle @WPI can be accessed off campus without a VPN. SMTP package was easy to get privileges to use for sending emails within procedures. Front-end Due to time constraints, ease of use for prototyping GUIs, and some limited experience in GUI design, we chose to use Java as a programming language and deployment platform. We are using Java Swing for GUI interface elements, and like the write once run many nature of Java byte code for portable reuse on any platform. Java Swing also has many predefined interface widgets that ease the GUI prototyping. We chose the free NetBeans (http://www.netbeans.org) as our IDE. NetBeans makes GUI design with Java Swing extremely quick as depicted in Figure 4. NetBeans also has built-in support for working with a CVS source code repository server. A set of sourceforge.wpi.edu accounts and a CVS repository for the project eStore were set up and configured for use with NetBeans to checkout and commit code. Originally we had planned on using Eclipse IDE with IBM Rational ClearCase version control, but the Visual Editor plug-in is not Figure 4: NetBeans JSwing widgets palette compatible with the most recent version of Eclipse. Even if using a beta version of the Visual Editor plug-in, working with the Visual Editor proved to be cumbersome compared to NetBeans. NetBeans proved to be very easy to learn and use, and we had a basic working Java Swing application written in minutes. The PAGE 8 OF 48 NetBeans design mode also set up action event listeners and associated methods by dragging and dropping the widgets allowing us to focus on DB access and application logic. The combination of ease of use for visual GUI design and integrated CVS support were the deciding factors vs. Eclipse. eStore is compiled to run on Java JRE's 1.5 and higher for the front-end with DB connectivity, though initially we had considered a web-browser based JSP/Servlet front-end. Time constraints and lack of experience with JSP and Servlets led us to use a Java Application for the prototype model. We're hoping to spend increasing time on the DB as the GUI design for the shopping cart and inventory stocking is implemented. Time permitting there may be some GUI surprises Additional Java, Swing, SQL references used: Java Swing: • http://java.sun.com/docs/books/tutorial/ui/features/components.html • Regarding beta Visual Editor for the latest Eclipse: http://wiki.eclipse.org/VE/Installing • http://download.eclipse.org/tools/ve/downloads/drops/R-1.2.3_jem200701301117/index.html • http://www.eclipse.org/vep/WebContent/faq.html • Graphing package—Scientific Graphics Toolkit (SGT) developed by and available from: http://www.epic.noaa.gov/java/sgt/ SQL: • For altering table: http://www.adp-gmbh.ch/ora/sql/alter_table.html • For integrity constraints: http://www.adp-gmbh.ch/ora/misc/integrity_constraints.html • For Oracle/PLSQL Topics: http://www.techonthenet.com/oracle/index.php • http://www.psoug.org/library.html • Attempt at dynamic cursors never implemented: http://www.oraclebase.com/articles/8i/NativeDynamicSQL.php • For insert: http://www.psoug.org/reference/insert.html • For cursors: http://infolab.stanford.edu/~ullman/fcdb/oracle/or-plsql.html#cursors • BLOB storage and access: http://www.go4expert.com/forums/showthread.php?t=866 • Oracle 8i The Complete Reference, Loney and Koch, Osborne McGraw Hill publishers. SMTP within Oracle: • For SMTP email access within Oracle: access granted to the UTL_SMTP package with the assistance of the WPI Oracle administrator: Mark Taylor • Sample code used to enable email sending within procedures from: http://www.databasejournal.com/features/oracle/article.php/3423431 • How to guide with overview: http://www.quest-pipelines.com/newsletter-v2/smtp.htm JDBC To connect the Java front-end application to Oracle we have chosen JDBC drivers supplied by Oracle. • Oracle - ojdbc14.jar o http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html PAGE 9 OF 48 If using MySQL we had planned: • MySQL – Connector/J o http://dev.mysql.com/downloads/connector/j/5.1.html Resources on How To Connect using JDBC: Sun JDBC tutorials: • http://java.sun.com/docs/books/tutorial/jdbc/basics/index.html • http://java.sun.com/developer/onlineTraining/Database/JDBCShortCourse/index.html • http://www.stardeveloper.com/articles/display.html?article=2003090401&page=1 • http://www.cs.unc.edu/Courses/comp118/docs/lessons/java/java_jdbc/ • http://web.njit.edu/all_topics/Servers/MySQL/Docs/mySample.java.html • http://dev.mysql.com/doc/refman/5.1/en/connector-j-examples.html • Our cs542 textbook provides some examples in Chapter 6 • Our cs542 course website has some resources: o http://web.cs.wpi.edu/~cs542/f07/links.html Guidance on reading and writing a BLOB using JDBC: • http://www.go4expert.com/forums/showthread.php?t=866 • http://www.oracle.com/technology/sample_code/tech/java/codesnippet/jdbc/clob10g/han dlingclobsinoraclejdbc10g.html#how1 • http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:23281415900 6#6891533553691 • Implementation Details ER Diagrams to SQL Tables We have converted almost all of our ER Diagrams to SQL Tables, and added Sequences and Triggers to aid in tracking unique IDs for many of the tables. Below is a listing of the tables, some with comments to describe design decisions. • Sequences in Oracle permit us to have auto-generated id numbers that are enforced by triggers upon initial insert. CREATE TABLE CUSTOMERS ( id INT PRIMARY KEY, fname VARCHAR(100) NOT NULL, lname VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, email VARCHAR(100) NOT NULL, passwd VARCHAR(100) NOT NULL, CONSTRAINT unique_cust_email UNIQUE (email), CONSTRAINT ck_cust_password CHECK (LENGTH(passwd) > 6)); • • Email is unique as it will be used as the logon identity for customers. Passwords must be greater than 6 characters. CREATE SEQUENCE customers_id_seq MINVALUE 1 PAGE 10 OF 48 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER customers_trg BEFORE INSERT ON CUSTOMERS FOR EACH ROW BEGIN SELECT customers_id_seq.nextval INTO :new.id FROM dual; END; / The Sequence/Trigger combination shown here is located throughout the table schemas, one for each table that contains an ID field. MySQL has the concept of an “auto-update” field, which is automatically set whenever a new item is inserted into the table. The value automatically increases, so each insert is one higher than the previous. Oracle does not contain an auto-update type, but by using a sequence and a pre-operation insert trigger we can simulate the type quite nicely. CREATE TABLE ADDRESSES ( id INT PRIMARY KEY, street_num INT NOT NULL, street_name VARCHAR(100) NOT NULL, street_name_2 VARCHAR(100), city VARCHAR(100) NOT NULL, state_abbr CHAR(2), zip VARCHAR(10), country VARCHAR(100) NOT NULL); • Design note: Both state_abbr and zip are not set to NOT NULL because an address that does not reside in the US may not have a state and zip code. CREATE SEQUENCE addresses_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER addresses_trg BEFORE INSERT ON ADDRESSES FOR EACH ROW BEGIN SELECT addresses_id_seq.nextval INTO :new.id FROM dual; END; / CREATE TABLE BILLING_INFOS ( id INT PRIMARY KEY, addr_id INT, ccard_type VARCHAR(30) NOT NULL, ccard_num CHAR(16) NOT NULL, ccard_exp CHAR(4) NOT NULL, CONSTRAINT unique_bi_addr UNIQUE (addr_id), CONSTRAINT fk_bi_addr FOREIGN KEY (addr_id) REFERENCES ADDRESSES(id) ON DELETE CASCADE); PAGE 11 OF 48 • Constraint: The billing_infos address must be unique, as an address can contain at most one billing information associated with it. CREATE SEQUENCE billing_infos_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER billing_infos_trg BEFORE INSERT ON BILLING_INFOS FOR EACH ROW BEGIN SELECT billing_infos_id_seq.nextval INTO :new.id FROM dual; END; / CREATE TABLE CUSTOMER_ADDRESSES ( cust_id INT, addr_id INT, CONSTRAINT CA_PK PRIMARY KEY (cust_id, addr_id), CONSTRAINT fk_ca_cust FOREIGN KEY (cust_id) REFERENCES CUSTOMERS(id) ON DELETE CASCADE, CONSTRAINT fk_ca_addr FOREIGN KEY (addr_id) REFERENCES ADDRESSES(id) ON DELETE CASCADE); • A customer can store many addresses in their account (customer address and shipping addresses), hence the reason the primary key is a combination of customer ID and address ID. CREATE TABLE CUSTOMER_BILLING_INFOS ( cust_id INT, billing_id INT, CONSTRAINT cbi_pk PRIMARY KEY (cust_id, billing_id), CONSTRAINT fk_cbi_cust FOREIGN KEY (cust_id) REFERENCES CUSTOMERS(id) ON DELETE CASCADE, CONSTRAINT fk_cbi_billing FOREIGN KEY (billing_id) REFERENCES BILLING_INFOS(id) ON DELETE CASCADE); • Likewise, a customer can store many billing_infos in their account; again, hence the reason the primary key is a combination of customer ID and billing ID. CREATE TABLE ORDERS ( id INT PRIMARY KEY, cust_id INT NOT NULL, addr_id INT NOT NULL, billing_id INT NOT NULL, status VARCHAR(30) NOT NULL, date_placed DATE NOT NULL, qty_items INT NOT NULL, calc_total_cost INT NOT NULL, shipping_cost INT NOT NULL, CONSTRAINT fk_ord_cust FOREIGN KEY (cust_id) REFERENCES CUSTOMERS(id) ON DELETE SET NULL, PAGE 12 OF 48 CONSTRAINT fk_ord_addr FOREIGN KEY (addr_id) REFERENCES ADDRESSES(id) ON DELETE SET NULL, CONSTRAINT fk_ord_billing FOREIGN KEY (billing_id) REFERENCES BILLING_INFOS(id) ON DELETE SET NULL, CONSTRAINT ck_status CHECK (status IN ('In Progress', 'Complete'))); • • • • • The cust_id is the customer who placed the order. The addr_id is the shipping address. The billing_id is the billing address (and its associated credit card information) for the order. The constraint ck_status having only two allowed values acts as a boolean operation, either the status is "In Progress" or "Complete". The ck_status constraint and associated column were removed from the DB and replaced with date_completed. Now an order ‘In Progress’ is a date_completed of value Null, and a ‘Complete’ order is a date_completed with a non-null value. Modifying the schema in place sometimes presented a challenge. Schema change since Project Progress Report: CREATE TABLE ORDERS ( id INT PRIMARY KEY, cust_id INT NOT NULL, addr_id INT NOT NULL, billing_id INT NOT NULL, status VARCHAR(30) NOT NULL, date_placed DATE NOT NULL, qty_items INT NOT NULL, calc_total_cost INT NOT NULL, shipping_cost INT NOT NULL, date_completed DATE, CREATE SEQUENCE orders_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER orders_trg BEFORE INSERT ON ORDERS FOR EACH ROW BEGIN SELECT orders_id_seq.nextval INTO :new.id FROM dual; END; / CREATE TABLE EMPLOYEES ( id INT PRIMARY KEY, fname VARCHAR(100) NOT NULL, lname VARCHAR(100) NOT NULL, addr_id INT NOT NULL, phone VARCHAR(20) NOT NULL, email VARCHAR(100) NOT NULL, username VARCHAR(100) NOT NULL, passwd VARCHAR(100) NOT NULL, emp_role VARCHAR(10) NOT NULL, CONSTRAINT fk_emp_addr FOREIGN KEY (addr_id) REFERENCES ADDRESSES (id) ON DELETE CASCADE, CONSTRAINT unique_emp_email UNIQUE (email), PAGE 13 OF 48 CONSTRAINT ck_emp_password CHECK (LENGTH(passwd) > 6), CONSTRAINT ck_role CHECK (emp_role IN ('EMP', 'ADMIN'))); CREATE SEQUENCE employees_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER employees_trg BEFORE INSERT ON EMPLOYEES FOR EACH ROW BEGIN SELECT employees_id_seq.nextval INTO :new.id FROM dual; END; / CREATE TABLE DEPARTMENTS ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, manager_id INT NOT NULL, dept_desc VARCHAR(300) NOT NULL, CONSTRAINT fk_manager FOREIGN KEY (manager_id) REFERENCES EMPLOYEES (id) ON DELETE SET NULL); CREATE SEQUENCE departments_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER departments_trg BEFORE INSERT ON DEPARTMENTS FOR EACH ROW BEGIN SELECT departments_id_seq.nextval INTO :new.id FROM dual; END; / CREATE TABLE PICTURES ( id INT PRIMARY KEY, pic_blob BLOB NOT NULL); CREATE SEQUENCE pic_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER pictures_trg BEFORE INSERT ON PICTURES FOR EACH ROW BEGIN SELECT pic_id_seq.nextval INTO :new.id FROM dual; END; / • The use of BLOB data (binary large objects) to store pictures is an area for future enhancement, but left in place to make the architecture extensible. PAGE 14 OF 48 CREATE TABLE PRODUCTS ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, description VARCHAR(100) NOT NULL, dept_id INT NOT NULL, weight INT NOT NULL, qty_in_stock INT NOT NULL, current_price INT NOT NULL, current_profit INT NOT NULL, markup_percent INT NOT NULL, shipping_cost INT NOT NULL, pic_id INT NOT NULL, CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES DEPARTMENTS(id) ON DELETE CASCADE , CONSTRAINT fk_pic FOREIGN KEY (pic_id) REFERENCES PICTURES(id) ON DELETE SET NULL , CONSTRAINT unique_pict_id unique (picture_id) , CONSTRAINT unique_prod_name unique (name) ); Note: the current_price attribute is an integer, not a float. All instances of money are stored in cents, not in dollars. There are two main reasons for this. First, integer math is significantly less expensive to perform than floating point operations (server side). With all of the algorithms we plan on running on all things money, speed may become an issue (especially if the sample space were to grow). Second, floating point is an imprecise value, and although we would only care about the first two decimal to three decimal places for precision, the database would store many more than that. This has the potential of introducing round-off errors if things lined up just right (or just wrong, depending on your point of view). So we decided to avoid the hassle of floating point and stuck with integers for monetary values. Originally we had the picture of the product within the PRODUCTS table as a Binary Large Object (BLOB). Unfortunately a “select *” query would be unable to complete, as the requesting entity would have to be able to handle the BLOB (SQLPlus would fail on the query, for instance). So we moved the picture out to its own table and reference it with a foreign key. We may not implement this, depending on time, but have kept the option there with the pictid attribute and pic_id_seq sequence. CREATE SEQUENCE prod_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER products_trg BEFORE INSERT ON PRODUCTS FOR EACH ROW BEGIN SELECT prod_id_seq.nextval INTO :new.id FROM dual; SELECT prod_pictid_seq.nextval INTO :new.pictid FROM dual; END; / CREATE TABLE ORDER_ITEMS ( id INT PRIMARY KEY, PAGE 15 OF 48 order_id INT NOT NULL, prod_id INT NOT NULL, quantity INT NOT NULL, unit_price INT NOT NULL, shipping_cost INT NOT NULL, date_shipped DATE NOT NULL, notified SMALLINT NOT NULL, CONSTRAINT fk_order FOREIGN KEY (order_id) REFERENCES ORDERS(id) ON DELETE SET NULL, CONSTRAINT fk_oi_prod FOREIGN KEY (prod_id) REFERENCES PRODUCTS(id) ON DELETE SET NULL, CONSTRAINT ck_oi_quantity CHECK (quantity > 0), CONSTRAINT ck_notified CHECK (notified BETWEEN 0 AND 1)); • • • The notified attribute is a SMALLINT to save space. We also verified the check constraint on a test table, and the constraint “Between 0 and 1” means the value may be set to 0 and 1 but nothing else. Notified was intended to indicate that email notification was sent to a customer that this part of the order had shipped, though it will remain an area for future work. Here is the test for "boolean like functionality" we tested: SQL> create table booltest 2 ( 3 notified smallint, 4 constraint ck_bool check (notified between 0 and 1)); Table created. SQL> insert into booltest (notified) values (1); 1 row created. SQL> insert into booltest (notified) values (2); insert into booltest (notified) values (2) * ERROR at line 1: ORA-02290: check constraint (DSAMPSON.CK_BOOL) violated SQL> insert into booltest (notified) values (0); 1 row created. SQL> insert into booltest (notified) values (-1); insert into booltest (notified) values (-1) * ERROR at line 1: ORA-02290: check constraint (DSAMPSON.CK_BOOL) violated CREATE SEQUENCE order_items_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER order_items_trg BEFORE INSERT ON ORDER_ITEMS FOR EACH ROW BEGIN SELECT order_items_id_seq.nextval INTO :new.id FROM dual; END; / PAGE 16 OF 48 CREATE TABLE INVENTORY_EVENTS ( id INT PRIMARY KEY, prod_id INT NOT NULL, date_recv DATE NOT NULL, product_cost INT NOT NULL, shipping_cost INT NOT NULL, quantity INT NOT NULL, active INT NOT NULL, qty_avail INT NOT NULL, CONSTRAINT fk_ie_prod FOREIGN KEY (prod_id) REFERENCES PRODUCTS(id) ON DELETE SET NULL, CONSTRAINT ck_active_boolean CHECK (active = 1 OR active =0), CONSTRAINT ck_ie_quantity CHECK (quantity > 0)); • • All inventory_events represent an addition of warehouse stock. Thus, an inventory event can never have a quantity of 0 or less. Added active and qty_avail to assist in profit calculation where an order_item spanned multiple inventory_events. Thus, we could keep track of when an inventory_event was no long represented by product.qty_in_stock, and how much qty_avail could be used in profit calculations. Quantity represents the actual amount of inventory provided by this inventory_event, and qty_avail is what remains over time until none of this inventory_event’s quantity remains. CREATE SEQUENCE inventory_event_id_seq MINVALUE 1 START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER inventory_event_trg BEFORE INSERT ON INVENTORY_EVENTS FOR EACH ROW BEGIN SELECT inventory_event_id_seq.nextval INTO :new.id FROM dual; END; / Triggers and stored procedures in PL/SQL are in the application demo section with discussion. Stored Procedures, Triggers and/or Algorithms (Pseudocode) • • Implemented in PL/SQL procedures and/or Java client side application. Additional procedures not listed here for example: calculate product.current_price based on recently added inventory_events that were purchased wholesale at a particular price, as well as calculating current_profit based on markup and wholesale price. Prototype Procedure for Order Fulfillment: • • • To be run once per day or manually by employees Can be run for a single ORDER or on all incomplete (IN_PROGRESS) ORDERS Can be run for a particular PRODUCT or on all PRODUCTS PAGE 17 OF 48 1. Check through order_item list to see if unsatisfied (date_shipped == NULL) orders can be fulfilled. 2. If order_item can be fulfilled (i.e. there is sufficient PRODUCT.qty for the item) a. Send email to employee and to customer b. Set date_shipped c. Decrement PRODUCT.qty Prototype Procedure for Customer Order Process: 1. 2. 3. 4. 5. 6. Customer browses departments and locates products When product found, ADD product to cart with desired QTY Go to step 1 as needed (while loop) SUBMIT order, notify user that order summary offered before commitment Select shipping address from existing, OR create new Select existing billing address (includes exactly 1 registered CCARD, offer to modify CCARD), or create new billing address (create new registered CCARD) 7. Display order summary 8. COMMIT or CANCEL 9. if CANCEL then go to step 1 10. if COMMIT a. BILL (returns ACCEPT or REJECT) b. if REJECT i. alert user and bring to step 6 again to re-select billing address/CCARD (option to modify) c. if ACCEPT i. Send email to confirm BILL and order summary with ship expectations ii. Set ORDER.status=IN_PROGRESS iii. Create exactly 1 ORDER_ITEM per PRODUCT in shopping cart@qty for this order_num iv. Clear shopping cart v. Trigger fulfillment procedure on this order_num vi. Goto step 1 Prototype Procedure for Customer Order History: 1. Display all orders currently IN_PROGRESS 2. Display all COMPLETE orders Prototype Procedure for Inventory Event: 1. Employee enters an INVENTORY_EVENT into the system 2. Activate Inventory Update Trigger a. Update PRODUCT.qty b. Calculate price/unit c. Update PRODUCT.cur_profit d. Run Order Fulfillment procedure for PRODUCT Application Details • Separate user interfaces for Employees and Customers. PAGE 18 OF 48 Original Prototype Employee Application • • DB Administrator can: o Create Employees and assign role. o Create Departments Set name, description, and manager, etc. o Create Products Set name, description, price, etc. CANNOT set quantity or current profit (derived by Inventory_Events) o Assign Product to a Departments. To do: o Initiate an Inventory Event when product is received. (See Inventory Event Procedure) o Run reports/queries/intiate daily tasks (stored procedures) o Refine/add functionality. Original Prototype Employee Application Screen Shots (deprecated) DBA must login to gain access to Oracle DB from Employees application. Upon logging in DBA can choose to see existing employees and update their records. Employee List is populated with a minial column query. Whereas, upon clicking an employee in the Employee List, their data is populated into the fields for easy editing and DB updates. PAGE 19 OF 48 Here we see Employees ID: 3, fname: V, last name: R, and ID: 2, fname: D, lname: S. Clicking an employee will populate the update fields in real time. This is to minimize DB reads (don't collect information about all employees, unless a specific employee record is needed at a particular time for update). To add a new employee, click "New Employee": Fill in fields, as well as role from drop down. Fields are validated prior to insert. The new employee will appear in the Employee List immediately. The Departments tab shows each department and deptid in the Department List. Similarly you can update the name or manager of the department and this will update in Oracle. PAGE 20 OF 48 Click to add New Department: Products tab has more to be implemented: This products list shows product ID, name, and department. Click to enter a New Product. PAGE 21 OF 48 Here we enter weight in ounces, markup > 4% (constraint in schema), cost to ship in cents, JFileChooser for the image, and the department (which normally shows with a department list). To assist with schema and SQL query testing this “Raw SQL” tab allows for sql syntax input and result output in the box. This is very convenient instead of having to switch to sqlplus during debugging. Customer Application as of Progress Report time: At this time, the majority of the customer application including shopping cart, checkout, billing, etc has not yet been implemented. At this time a customer can only create an account and login: Current screen shots are listed below are active and function: PAGE 22 OF 48 Shop, Cart, Account Management are minimally implemented at this time: From prototype drawing then later to implementation: • As of project progress report customers could only: o Create an account o Login PAGE 23 OF 48 The Customer eStore Application Today (with trigger/procedure discussion) Login: Includes password recovery, and account creation. Upon successful login customer is now presented with 3 modes of operation (below): 1) Shop for Products 2) View Cart (shopping cart with items added) 3) Account Management (to view shipping/billing addresses, credit cards used, order status 1) Shop for Products: A tree view of all products, and then more details about the selected product along with the ability to add it to the cart: Select items, select quantity, add to cart, repeat: A second view mode for finding products and adding to cart, Most Popular Items: Click Most Popular Items from the shop mode to see most popular items by common time frames. PAGE 24 OF 48 Again, pick a quantity, and add these popular items to your shopping cart. 2) View Cart mode Now let’s view our cart: We can make choices about what items we want to keep out of the selection made, return to Shop for more Products, or Proceed to Checkout. 3) Checkout (3 steps) 3.1) Select Shipping Address or Cancel Checkout Choose an existing address or make a new one, then proceed to billing or cancel: PAGE 25 OF 48 Æ or or . Then, click proceed to billing: 3.2) Select Billing Address or Cancel Checkout Choose an existing billing address with associated credit card or make a address/associated credit card, then proceed to checkout summary: Æ , or or . Then, click Proceed to Order Confirmation: . 3.3) Confirm Order and Submit Review the final checkout summary including shipping information, billing information, items ordered, and shipping costs. When ready submit order or cancel (see below): PAGE 26 OF 48 Once the order has been submitted the customer application can view your order status. 4) Account Management has the following option tabs: 4.1) User Info: Change customer information (first name, last name, phone#, password). 4.2) Shipping Addresses: add new addresses, or view existing. 4.3) Billing Info: view existing billing addresses and associated credit cards or add new. 4.4) Past Orders: Check on A) Orders In Progress and B) Completed Orders PAGE 27 OF 48 Orders in Progress will always show as Completed: In Progress. To get more details and check on the status of order_items (individual products) that make up this order, and whether or not they have shipped click View, . We can see the Items Ordered: The Shipping & Handling is calculated by sum of: (shipping_cost of each product)*(qty ordered) eStore Order Fulfillment Theory eStore does not allow customers to see whether or not a product is in stock at the time of order. Our goal is to fulfill orders as quickly and fairly as possible, but ensure: 1) All quantity of an order_item (1 product) must ship together at once. 2) If an order_item is not in stock, only when an inventory event that increments the product.qty_in_stock to at minimum the order_item.quantity will attempt to ship the item. Caveats: 3) If a large order_item comes in, we will not hold up smaller order_items (from other orders). 4) It is the responsibility of the eStore admin to provide enough inventory to ship all order_items. Email notification and a query is used to facilitate this. Re-notification will take place if still not enough inventory is available to ship order_items. Example Order @ Submit Order Time Two items were added to our cart: “Guitar Hero” and “Legend of Zelda”, (products #6 and #7) respectively, then we submitted the order. Here’s what was in stock at the time of order submission: PAGE 28 OF 48 SQL> select id, qty_in_stock from products where id=6 OR id=7; ID QTY_IN_STOCK ---------- -----------6 0 7 3 We currently have qty_in_stock 3 of Guitar Hero and qty_in_stock 0 of Legend of Zelda. Because inventory was available to ship the entire order_item for Guitar Hero, but no inventory to ship Legend of Zelda the following should occur: 1) Guitar Hero (product#7) has 3 in stock, and should ship immediately 2) Legend of Zelda (product#6) will have to wait for an inventory_event so that it can ship. The customer is guaranteed the product.current_price at order time. In theory we could loose profit on this but we anticipate most inventory costs will stay fairly stable. From the detailed Orders In Progress (Account Management) view, we see that as expected , and Legend of Zelda Guitar Hero shipped with a ship date of 2007-12-13 (which is out of stock) has not shipped . What Happens In Oracle When Order Is Submitted The GUI app does an insert on order. The trigger: orders_trg (before insert) sets the order.date_placed, and sets the order.date_completed to null, as well as assigns the order.id from the sequence: orders_id_seq with a unique value as primary key. Then each order_item that makes up this order is submitted with the new order id, recall it’s one order_item per product with multiple quantity. Here’s what happens: Constraint: ck_oi_quantity ensures that there are more than 0 being ordered, constraint: ck_notified ensures notified is 0 or 1 (but this is for future extension to send email to customers to notify them of ship status), and sequence: order_items_id_seq generates an order_item.id. Before insert on order_items trigger: ship_order_items_inv_evt tries to ship the order_item. CREATE OR REPLACE TRIGGER SHIP_ORDER_ITEMS_INV_EVT BEFORE INSERT ON ORDER_ITEMS FOR EACH ROW DECLARE qtyInStock NUMBER(38); profit INT; p_name VARCHAR2(100); BEGIN /* Get the product quantity available in the database */ SELECT p.QTY_IN_STOCK, p.name INTO qtyInStock, p_name FROM PRODUCTS p WHERE p.id=:new.prod_id; /*If we have enough in stock to ship this order_item ship it!*/ IF (qtyInStock>=:new.quantity) THEN /*call procedure to calculate profit*/ CALC_ORDER_ITEM_PROFIT(:new.id, :new.prod_id, :new.quantity, :new.unit_price, :new.shipping_cost, profit); :new.profit:=profit; /* Flag order_item as shipped */ SELECT sysdate INTO :new.date_shipped FROM dual; /* Update the new product.qty_in_stock */ PAGE 29 OF 48 UPDATE PRODUCTS p SET p.qty_in_stock = p.qty_in_stock - :new.quantity WHERE p.id=:new.prod_id; ELSE /*not enough in stock to ship this item, send notification to admin*/ send_oi_stock_qty_notify (:new.id, :new.quantity, qtyInStock, p_name); END IF; RETURN; END; / Essentially: if we have enough in stock for this product, ship the order_item right away, calculate the profit for this order_item that we made using calc_order_item_profit() procedure, flag the order_item as shipped by setting the order_item.date_shipped with the current date/time, update the product.qty_in_stock (decrement by qty shipped). Thus Guitar Hero shipped (qty_in_stock has decreased by 1) see here: SQL> select id, qty_in_stock from products where id=6 OR id=7; ID QTY_IN_STOCK ---------- -----------6 0 7 2 In the case of Zelda which was not in stock, an email was received by the admin in their email server’s mailbox per send_oi_stock_qty_notify(): Date: Thu, 13 Dec 2007 00:35:26 -0500 From: [email protected] Message-Id: <[email protected]> Subject: Product requires more inventory qty to ship! From: [email protected] Date: 12/13/2007 00:35:26 To: [email protected] CC: [email protected] Order Item# 84 requires the following quantity in stock to ship the order_item: 1 However, only 0 of product are in stock at this time. Please add inventory for the product: "Legend of Zelda: Twilight Princess." Product "Legend of Zelda: Twilight Princess" requires 1 more inventory qty to ship! **ATTN: service machine, please do not reply.** This informs the admin of the store which order_item is being held up by how much quantity of a product that is not in stock. The action the admin must take is to add an inventory event which we’ll see later. For the Guitar Hero which did ship let’s see how the profit was calculated: CREATE OR REPLACE PROCEDURE CALC_ORDER_ITEM_PROFIT ( OI_ID IN NUMBER, OI_PID IN NUMBER, OI_QTY_REQD IN NUMBER, OI_UPRICE IN NUMBER, OI_SHPPRC IN NUMBER, OI_PROFIT IN OUT NUMBER) IS /* IN OUT parameters allow for a value to be passed in and a value to be modified by reference upon completion */ ie_id NUMBER; ie_price NUMBER; ie_shpcost NUMBER; ie_qty NUMBER; PAGE 30 OF 48 QTY_REQD INT; /* this cursor shows us inventory_events which still represent active qty_in_stock ordered by date_recv for the product of this order_item */ CURSOR active_inv_evts IS SELECT id, product_cost, shipping_cost, qty_avail FROM inventory_events WHERE active=1 AND prod_id=OI_PID ORDER BY date_recv ASC; BEGIN QTY_REQD := oi_qty_reqd; oi_profit :=0; OPEN active_inv_evts; LOOP FETCH active_inv_evts INTO ie_id, ie_price, ie_shpcost, ie_qty; /* If there are no more rows to fetch, exit the loop: */ EXIT WHEN active_inv_evts%NOTFOUND; IF (QTY_REQD > 0) THEN /*if order_item can be fulfilled by the first inventory_event entirely*/ IF (QTY_REQD <=ie_qty) THEN /*what we made on the item less the costs to stock the item and ship it*/ oi_profit:=oi_profit+((OI_UPRICE+OI_SHPPRC-ie_priceie_shpcost)*QTY_REQD); /*If this inventory_event will be exactly consumed by this order_item*/ IF (QTY_REQD=ie_qty) THEN /*set this inventory_event to inactive*/ UPDATE inventory_events set active=0, qty_avail =0 where id=ie_id; ELSE /*we must update what we used of this inventory_event and continue with loop*/ UPDATE inventory_events set qty_avail = qty_avail QTY_REQD where id=ie_id; END IF; /*if QTY_REQD<ie_qty (not a full inventory event) don't set to inactive*/ QTY_REQD :=0; ELSE /*QTY_REQD>this inventory_event_qty*/ oi_profit:=oi_profit+((OI_UPRICE+OI_SHPPRC-ie_priceie_shpcost)*ie_qty); /*what we made on the item less the costs to stock the item and ship it for this inventory event*/ QTY_REQD:=QTY_REQD-ie_qty; UPDATE inventory_events set active=0, qty_avail = 0 where id=ie_id; END IF; END IF; /*we had some qty*/ END LOOP; /*all inventory_events processed for this order_item*/ END; / Case 2 (order_item could not ship, send notification): For the Zelda order_item which did not ship here’s how we sent the email to admin: CREATE or REPLACE PROCEDURE send_oi_stock_qty_notify (OI_ID IN NUMBER, OI_QTY IN NUMBER, P_QTY IN NUMBER, P_NAME IN VARCHAR2) IS /*REF: code adapted from http://www.databasejournal.com/features/oracle/article.php/3423431 */ /*October 21, 2004 "Sending e-mail from within Oracle" By James Koopmann*/ PAGE 31 OF 48 mailHOST mailFROM mailTO mailCC mailSBJ mailCONN mailDATE vreply vreplies i VARCHAR2(64):= 'smtp.wpi.edu'; VARCHAR2(64):= '[email protected]'; VARCHAR2(64):= '[email protected]'; VARCHAR2(64):= '[email protected]'; VARCHAR2(64):= 'Product requires more inventory qty to ship!'; utl_smtp.connection; VARCHAR2(20); utl_smtp.reply; utl_smtp.replies; number; BEGIN SELECT TO_CHAR(SYSDATE,'MM/DD/YYYY HH24:MI:SS') INTO mailDATE FROM dual; vreply := utl_smtp.open_connection(mailHOST, 25, mailCONN); vreplies := utl_smtp.help(mailCONN, 'HELP'); for i in 1..vreplies.count loop dbms_output.put_line( 'code = ' || vreplies(i).code ); dbms_output.put_line( 'text = ' || vreplies(i).text ); end loop; vreplies := utl_smtp.ehlo(mailCONN, mailHOST); for i in 1..vreplies.count loop dbms_output.put_line( 'code = ' || vreplies(i).code ); dbms_output.put_line( 'text = ' || vreplies(i).text ); end loop; vreply := utl_smtp.mail(mailCONN, mailFROM); vreply := utl_smtp.rcpt(mailCONN, mailTO); vreply := utl_smtp.open_data(mailCONN); /*construct email message and write to SMTP connection stream*/ utl_smtp.write_data(mailCONN, 'Subject: '||mailSBJ || chr(13)); utl_smtp.write_data(mailCONN, 'From: '||mailFROM || chr(13)); utl_smtp.write_data(mailCONN, 'Date: '||mailDATE || chr(13)); utl_smtp.write_data(mailCONN, 'To: '||mailTO || chr(13)); utl_smtp.write_data(mailCONN, 'CC: '||mailCC || chr(13)); --utl_smtp.write_data(mailCONN, 'BCC: '||mailFROM || chr(13)); utl_smtp.write_data(mailCONN, chr(13)); utl_smtp.write_data(mailCONN, 'Order Item# '||OI_ID||' requires the following quantity in stock to ship the order_item: '|| OI_QTY || chr(13)); utl_smtp.write_data(mailCONN, 'However, only '||P_QTY||' of product are in stock at this time. Please add inventory for the product: "'|| P_NAME ||'."'||chr(13)); utl_smtp.write_data(mailCONN, chr(13)); utl_smtp.write_data(mailCONN, 'Product "'||p_name||'" requires '||(OI_QTY-P_QTY)||' more inventory qty to ship!' ); utl_smtp.write_data(mailCONN, chr(13)); utl_smtp.write_data(mailCONN, '**ATTN: service machine, please do not reply.**' || chr(13)); vreply := utl_smtp.close_data(mailCONN); vreply := utl_smtp.quit(mailCONN); END; / This required package permission was granted by WPI’s Oracle admin as indicated earlier and access to the WPI SMTP server, many thanks. Next, the GUI app executes the procedure: check_order_complete(order.id). This takes in the order.id and will set it’s order.date_completed to today only if the nested query (return the count of all order_items for this order whose date_shipped is NULL (unshipped)) is 0. CREATE OR REPLACE PROCEDURE CHECK_ORDER_COMPLETE ( ORD_ID IN NUMBER) PAGE 32 OF 48 AS oi_count INT := 0; BEGIN UPDATE ORDERS SET date_completed=sysdate WHERE id=ord_id AND 0=( SELECT COUNT(order_id) FROM ORDER_ITEMS WHERE order_id=ord_id AND date_shipped IS NULL); RETURN; END; / As an admin, we’ll add an inventory_event to stock the Zelda product: Note that the new inventory_event has been added, but the stock level remains at 0: This is because the 1 item in stock allowed the one order_item to ship: SQL> select * from order_items where order_id=61; ID ORDER_ID PROD_ID QUANTITY UNIT_PRICE SHIPPING_COST DATE_SHIP ---------- ---------- ---------- ---------- ---------- ------------- --------NOTIFIED PROFIT ---------- ---------83 61 7 1 4290 500 13-DEC-07 0 390 84 0 61 378 6 1 4158 500 13-DEC-07 What happened behind the scenes? Before insert on inventory_events trigger: inventory_event_trg fires: CREATE OR REPLACE TRIGGER INVENTORY_EVENT_TRG PAGE 33 OF 48 BEFORE INSERT ON INVENTORY_EVENTS FOR EACH ROW DECLARE old_qtyInStock NUMBER(38); old_price NUMBER(38); old_shipcost NUMBER(38); percent NUMBER(38); new_qtyInStock NUMBER(38); new_price NUMBER(38); new_shipcost NUMBER(38); new_profit NUMBER(38); old_profit NUMBER(38); BEGIN SELECT inventory_event_id_seq.nextval INTO :new.id FROM dual; /*date of inventory event is now*/ SELECT sysdate INTO :new.date_recv FROM dual; /*get prior information about this product so we can update to reflect new*/ SELECT qty_in_stock, current_price, shipping_cost, markup_percent, current_profit INTO old_qtyInStock, old_price, old_shipcost, percent, old_profit FROM PRODUCTS where id = :new.prod_id; new_qtyInStock := (old_qtyInStock+:new.quantity); /*new price takes into account existing price for remaining quantity and price of new quantity being added as well as the markup percent for this product*/ new_price := ((old_qtyInStock*old_price)+(:new.quantity*(:new.product_cost*(percent+100)/10 0)))/new_qtyInStock; /*new shipping cost takes into account existing shipping cost for remaining qty and new quantity at new rate*/ new_shipcost := (((old_qtyInStock*old_shipcost) + (:new.quantity*:new.shipping_cost))/(new_qtyInStock)); /*estimate of profit for this product incorporating new and old*/ new_profit:=old_profit+(:new.quantity*(new_price-old_price)); UPDATE products SET current_price= new_price, shipping_cost = new_shipcost, qty_in_stock= new_qtyInStock, current_profit=new_profit WHERE id=:new.prod_id; END; / Now the trigger: invevt_scan_orders_trg fires (after insert) on inventory_events: CREATE OR REPLACE TRIGGER INVEVT_SCAN_ORDERS_TRG AFTER INSERT ON INVENTORY_EVENTS FOR EACH ROW DECLARE qtyInStock NUMBER(38); oi_id NUMBER(38); oi_qty NUMBER(38); p_name VARCHAR2(100); /* cursor gets order_items that process the same product and have not been shipped ordered by the order date */ CURSOR unshipped_oi IS SELECT oi.id, oi.quantity FROM ORDERS o, ORDER_ITEMS oi WHERE oi.prod_id=:new.prod_id AND oi.date_shipped IS NULL AND oi.order_id=o.ID ORDER BY o.date_placed ASC; BEGIN /* get qty_in_stock for the product*/ SELECT p.QTY_IN_STOCK, p.name INTO qtyInStock, p_name PAGE 34 OF 48 FROM PRODUCTS p WHERE p.id=:new.prod_id; OPEN unshipped_oi; LOOP FETCH unshipped_oi INTO oi_id, oi_qty; /* If there are no more rows to fetch, exit the loop: EXIT WHEN unshipped_oi%NOTFOUND; /*inventory was available to ship the item*/ IF (qtyInStock>=oi_qty) THEN /* Flag to be shipped with the date*/ UPDATE ORDER_ITEMS oi2 SET oi2.date_shipped=sysdate WHERE oi2.id=oi_id; /* Update the new product.qty_in_stock */ UPDATE PRODUCTS p SET p.qty_in_stock = p.qty_in_stock - oi_qty WHERE p.id=:new.prod_id; */ /*decrement qtyInStock that we originally queried for its value since we’re in a loop and may need to ship another order_item for this product*/ qtyInStock:=qtyInStock-oi_qty; ELSE /*not enough in stock to ship this order_item, send notification to admin to stock more of this product*/ send_oi_stock_qty_notify (oi_id, oi_qty, qtyInStock, p_name); END IF; END LOOP; /*close cursor */ CLOSE unshipped_oi; END; / Now this order should be complete, let’s check in the customer’s account management: We can see that order#61 with both order_items has shipped. PAGE 35 OF 48 Click view to confirm shipment has shipped by finding Ship Dates: Let’s check our profit for these order_items: SQL> select id, order_id, prod_id, quantity, unit_price, shipping_cost, profit from order_items where order_id=61; ID ORDER_ID PROD_ID QUANTITY UNIT_PRICE SHIPPING_COST PROFIT ---------- ---------- ---------- ---------- ---------- ------------- --------83 61 7 1 4290 500 390 84 61 6 1 4158 500 378 It appears including the markup, we made around 4 dollars of profit per order_item. Good job! Additional profit costing example (multiple inventory_events per order_item): Added 10 MacBooks to the inventory as follows: Date ---------2007-12-09 2007-12-09 2007-12-09 | Qty | Price | Shipping | --- | --------- | ---------| 3 | 1300.00 | 50.00 | 4 | 990.00 | 60.00 | 3 | 1000.00 | 50.00 PROD_ID QUANTITY QTY_AVAIL SHIPPING_COST PRODUCT_COST ID ---------- ---------- ---------- ------------- ------------ ---------12 3 3 5000 100000 105 12 4 4 6000 99000 106 12 3 3 5000 130000 107 Customer buys 5 MacBooks at 1172.88 each (current_price) at shipping_cost of 270 for the 5, thus an order total is 6134.40. Our actual cost was 5250 (3@(1000+50)+2@(990+60)) difference is 884.4 profit on this order see below that trigger+procedure worked in calculating profit: SQL> select * from order_items where order_id=48; ID ORDER_ID PROD_ID QUANTITY UNIT_PRICE SHIPPING_COST ---------- ---------- ---------- ---------- ---------- ------------DATE_SHIPPED NOTIFIED PROFIT -------------------- ---------- ---------67 48 12 5 117288 5400 09-Dec-2007 22:26:28 0 88440 PAGE 36 OF 48 OK, so we have 5 in stock remaining (2 from inventory_event.id#106 + 3 from event#107): PROD_ID QUANTITY QTY_AVAIL SHIPPING_COST PRODUCT_COST ID ---------- ---------- ---------- ------------- ------------ ---------12 4 2 6000 99000 106 12 3 3 5000 130000 107 inventory_event 106 shows prod_id 12 (macbook) was stocked with quantity: 4, but only 2 are now qty_available. Queries The following table illustrates queries directly accessed in GUI, many other queries are used to collect data and construct GUI elements behind the scenes (such as products within departments). Description Customer order history (customer account management) Shipping status for each part of a specific order (customer account mgmt) Top 10 best selling items (Employee can see top 10, customer can see top 5). • By week/month/all time Timeline: Profit • By Product • By Customer • Overall Timeline: Inventory Level • By Product (within inventory_events and stats) • All Products Combined (Overall) (stats) Average time to fulfill an order Highest ship-to locations • By country/state/city Unfulfilled orders due to order_items without inventory in stock to ship. Daily Reports • Highest selling products o By Quantity o By Profit • Highest buying customers o By Quantity o By Amount Spent Where to run? Customer Customer Both Employee (stats) Employee Employee (stats) Employee (stats) Employee (stats) Employee (stats) Employee Application and eStore Statistics Screen shot below of basic functionality of Employee application: We can add or modify: Employees, Departments, Products, Inventory_Events, or execute SQL commands. Login as admin (password is admin1234): PAGE 37 OF 48 now we see the Welcome screen: . Inventory Update allows us to add inventory_events for products: Edit Departments allows us to modify or add a new department: Æ PAGE 38 OF 48 Edit Products allows us to modify existing products or add new ones: We can see that a markup can be changed. This value will be applied to actual costs in calculating current_price (the price the customer pays) and allows eStore to make a profit. Markup is only applied upon new inventory_event insertions. If the admin were to change the markup now, it would apply to the next inventory_events, not existing. Note: pictures stored as Binary Large Objects (BLOBs) have not been implemented at this time, but the table that stores them, and the primary key/foreign key relationship exists with the products table should we choose to insert them at a later time in the GUI. Add New Product is below: Note: all forms in the GUI have data validation as well as Oracle schema constraints that will be caught and passed as errors via GUI exceptions. , . PAGE 39 OF 48 Employees can be modified or added (see below): Note: if Role: specifies EMP (employee) instead of ADMIN (administrator) this store worker will not be able to login to the Employee application. This is solely permitted for the ADMINs. New Employees can be added as follows: Note that the password did not meet the Oracle schema constraint: Making the password the required length allows the insert to complete. Employee App Statistics View Via the Statistics and Reports button in the Management window of the Employee application, an eStore employee is able to view a number of interesting statistics and usage patterns. Product Stats Here the employee can see the top selling products of the past 24 hours, the past week, the past month, the past year, or of all time. The results can be sorted by quantity sold or profit made. The query to the database is across the Order_Items table, of Order_Items that have shipped within the given timeframe. Since the Date is an SQL object it cannot be used directly in the query string when using JDBC and a java.sql.PreparedStatement must be used. The PreparedStatement object is created using the basic query string with a ‘?’ substituting the date. Then the setDate method of the PreparedStatement is called, passing it the Date required for the query. In this case, it is a date based on the value of the time drop-down box – the current PAGE 40 OF 48 date/time minus 24 hours, the current date/time minus 7 days, etc. Then getting the name of the product is a simple join with the Products table based on the product ID stored in the Order_Item. The result of this can then be displayed sorted by quantity or sorted by profit made. Changing the sorting criteria does not reissue the query; it simply changes how the data is displayed. Also included in the Product Stats is a list of the top buying customers of the past 24 hours, the past week, the past month, the past year, or of all time. This is a select from the Orders table of all orders that are complete and are within the specified time frame. The data includes how much the customer spent and how many unique items the customer bought, and the name of the customer is obtained via a join with the Customers table using the customer ID. The results of this can be sorted by the amount spent or by the number of unique items purchased, and again changing the sorting criteria does not reissue the query. Continued from above: Inventory Stats The window shows a line graph of the inventory level of a product (or all products together) over time. Submitted Inventory_Events cause inventory levels to rise, and shipped Order_Items cause inventory levels to fall. If a single product is selected the time span shown is only for that particular product; it begins with the first arrival into inventory and ends with the last time an Order_Item was shipped. If all products are selected the time span is over the life of all products, beginning with the arrival of the first product and ending with the shipping of the most recent Order_Item. PAGE 41 OF 48 The line graphs were made possible thanks to the Scientific Graphics Toolkit (SGT) from the National Oceanic and Atmospheric Administration (NOAA). The SGT graphics package is available free for use at http://www.epic.noaa.gov/java/sgt/ The SGT facilitates graph creation and handles much of the 2D graphics involved; the user simply must supply properly formatted data in the form of an SGTData model. Multiple sets of SGTData objects can be added to a single graph, and this is how the inventory levels of all products is built; the application loops through all products and builds an SGTData object for each one. If only a single product is select only one SGTData object is built. The building of the data object for the graph is as follows. First the date and quantity for each Inventory_Event for the product are retrieved. Then the date and quantity for each shipped Order_Item for the product are retrieved. All instances of the two event types are stored together in a single list and are sorted by date. A running total of the inventory level is initialized, and as each event is processed the inventory level goes either up (in the case of an Inventory_Event) or down (in the case of an Order_Item). At each event the date/time is stored, as is the inventory level at that particular point in time. These two data lists are then fed to the SGT package and an SGTData object is created to model the data. This object is fed to the 2D plot object and a graph is created. In the case the employee wishes to view the inventory levels of all products simultaneously, the previous process is done once for each product, and all SGTData objects are collected and fed into the same 2D plot object to create the complete graph. PAGE 42 OF 48 Order Fulfillment Stats The Order Fulfillment window shows the highest ship-to locations and the average time required to fulfill an order. The highest ship-to locations can be sorted by country, state, or city. The retrieval and then display of this data is a multi-step process. First a select is done across all completed orders in the Orders table. For each result it retrieves the shipping address ID and the order ID. The shipping address ID is then used to look into the Addresses table to get the address information. At the same time the order ID is used over the Order_Items table to get all items in the order, and quantity for each Order_Item is retrieved. If the address is unique a new Order Address object is created with the given address and quantity. If this address has been seen before the current quantity is added to the existing Order Address. Once the data is completely retrieved it can be displayed to the employee. Depending on whether the employee wants to group by country, state, or city, the data list is traversed looking for each unique entry (country, state, or city). The quantity for each unique entry is tallied up and the data is displayed to the user. PAGE 43 OF 48 The Average Time to Fulfill an Order calculation simply queries all completed orders from the Orders table, totals the difference of data completed minus date placed, and takes an average. The time is displayed by number of days, hours, minutes, and seconds. Profit Stats The final statistics window allows the viewing of profit over time, on a per-product basis or for all products, and on a per-customer basis or for all customers. The building of the graph is done the same way as the graph of the Inventory Level statistics; the data used to create the SGTData objects is different, of course. But like the Inventory Level graph the Profit graph is created with either a single line (a single product) or with multiple lines (all products). For each product line a select is done over the Order_Items table looking for shipped items of the particular product. The order ID, data shipped, and profit calculated are all retrieved. The order ID is only used if the line is being built in reference to a specific customer; if it is the order ID must be checked against the customer ID in the Orders table, and the data is used only if there is a match. If the data is going to be used, either because the customer ID matches or all customer data is being used, the date shipped and the profit are stored in an Order Item Event object. A list of these objects is created and sorted by date. The SGTData object is then created using the time span as a base and the profit as data points. Once created each SGTData object is added to the 2D plot, and once all objects have been created the plot is displayed to the user. Profit for All Products over all time based on David Sampson’s purchases: PAGE 44 OF 48 Profit for Guitar Hero over all time based on All Customers purchases: Profit for All Products over all time based on All Customers purchases: Earlier discrepancies in the data were caused by updates of how profit was calculated (or not at all) from earlier in the testing. The timeline indicates the end of November through December PAGE 45 OF 48 12th. Also, because some big ticket items have hundreds of dollars of profit, the items with smaller profit margins (of less than 10 dollars) are difficult to see on this scale. Additional charting can be extended in future work, as this was intended as a proof of concept, and was not in the original scope. It was an exciting addition, allowing some visualization of the data. Unfulfilled Order Stats This can be useful to an admin who has not checked their email in some time or wants an up-todate summary of which orders are being held up for which order_items, and which products and quantities are needed to fulfill them, sorted by date placed. Here is the query: SELECT o.id AS ORDER_ID, oi.id AS ORDER_ITEMID, oi.prod_id AS PROD_ID, oi.quantity AS QTY_ORDERED, p.qty_in_stock AS QTY_IN_STOCK, o.date_placed AS DATE_PLACED FROM order_items oi, products p, orders o WHERE oi.date_shipped IS NULL AND oi.order_id=o.id AND oi.prod_id=p.id ORDER BY o.date_placed Example output: Order Item #45 of Order #38 (placed at 2007-12-08 22:33:14.0) requires 7 more of product ID 9 (LEGO Harry Potter Hogwarts Castle) to ship. Testing Utilizing ourselves and friends, perform white box testing (testing via the GUI), we tested: 1. Create Employees, Departments, Products 2. Generate inventory_events 3. Generate Customer Data (orders) 4. For each of the above verify that the following are honored: 1. Schema integrity contraints honored as well as Primary/Foreign Key relationships. 2. Triggers before and after perform as expected (pre and post condition met) 3. Procedures perform as expected when called individually or from within a trigger. 4. Business logic is honored: calculation of profits, qty_in_stock, markup% upon current_price, relationship between customer data with their respective orders and order_items. 5. Business process flow working as expected (add products, view cart, cancel checkout, all of checkout process, submit of order, check of order status, etc. 5. Run queries, verify they work. 6. Verify email procedure and Oracle SMTP functionality works. 7. Repeat for different use cases. • A working system will allow for creation of all necessary entities, and produce customer order history that is queryable. Def: white box testing—“The tester chooses test case inputs to exercise paths through the code and determines the appropriate outputs.” Source: http://en.wikipedia.org/wiki/White_box_testing Accomplishments & Comments • Learning Swing UI design has been a challenge, getting JDBC to work, picking an IDE and DB system, setting up the source code repository, debugging SQL and schema syntax errors, and planning the original ER diagram were a lot of effort, but quite enjoyable. PAGE 46 OF 48 Emphasizing a strong initial project proposal, and identifying preliminary details allowed us to begin prototyping rapidly. • A simple missing “space” in an SQL statement formed in the GUI front end took nearly 2 hours to debug. The most mundane things can at times be perplexing! • We agree that we would work in a similar manner if we were to do this again, it has thus far been a good learning experience, however schema design is very challenging, and making changes after the fact can be tricky! 9 We both worked hard to collaborate and have equal contribution to the project. Schedule Goals Completed as of Project Progress Report: 11/8 (Week 10 of cs542): • DB: o Finalized ER diagram and entity attributes o Construct relational model (table design) o Built SQL tables translating ER model o DB transaction management tutorials/reference materials o Finalized using Oracle vs. MySQL • Java Application front-end: o Set up source code control repository using SourceForge CVS o Java Swing refresher/practice o Confirmed database access using JDBC • Basic Data Entry Application o Create a Java Swing application to configure the database for our application and being creating data Create all tables, sequences, and basic triggers Allow employee creation complete with address Allow department creation Allow product creation “Raw” mode to do manual SQL queries and commands o Designed to be converted into the Employee Application with minor modifications o Employee, Department, and Product data input has begun • Customer Application o Designed from scratch using lessons learned creating the Basic Data Entry Application o Database access is hidden from the customer, making it look like a true app. o Customers can create an account for themselves and log in o Mockups (sketches) are complete for shopping user interface o Application currently logs most SQL commands to a file, to ease data replication across databases Goals Completed Since Project Progress Report: Completion goal by 11/15 (Week 11): • Continue work on Customer Application, add product browsing, shopping cart, and order placement. • Finish Data-Entry Application. PAGE 47 OF 48 • • • • • • • • • • • Start converting Data-Entry Application into Employee Application. • Begin working out more complex queries. • Continue adding data, mostly products, and begin making customer orders. • Plan to implement stored procedures to handle orders once inserted. Completion goal by 11/22 (Week 12): • Finalize the more complex queries and procedures. • Complete Employee Application. • Complete Customer Application, adding account management and configuration. • Start running through use cases/debugging. • Continue to add customer order data and increase sample set. • Start building query output reports. • Invite friends to participate in usability studies and assist in data creation. Completion goal by 11/29 (Week 13): • Continue to generate data and evaluate effectiveness of queries/output/reporting. • Additional use cases (data points). • Incorporate input from family and friends. • Begin Final Project report. • Begin demonstration slides / demo planning. Completion goal by 12/6 (Week 14): • Final debugging. • Take UI work flow screen shots and construct sample work flow. • Finalize written report and Submit Final Project Report. • Other Deliverables: o Environmental setup/DB setup. o Schema. o Sample data loading/bulk loader. o Java Application with directions. o Source code o UI screen shots/run demo ¾ Extras: o SMTP email generation within stored PL/SQL procedures on Oracle o Graphic charting of queries ¾ Database concepts utilized: Entity and Relationship models • PL/SQL Language Usage Schema Design • Oracle sys_date from dual table Primary Key / Foreign Key Relationships, • Nested Queries Table Level Integrity Constraints • SQL concepts GROUP BY (aggregation), ORDER BY, Ascending vs. Descending Sequences • alter of schema after data loaded Triggers (before and after) • Concurrency With Commit (JDBC by default Procedures with IN, and IN OUT (used to pass commits transacations) values and references) • error resolution when defining triggers and JDBC Connectivity for queries and procedure procedures using show <type> errors execution • Binding new: variables on before insert triggers Static Cursors to traverse tables for each row SMTP Package of Oracle PAGE 48 OF 48