Steve Kirk
Microsoft Developer Network
May 1998
Summary: Describes the Order Entry application. (13 printed pages) Knowledge of Visual Basic and ADO will be helpful. Explains:
This article describes the Order Entry application used in Phase 1 of the Duwamish Books sample.
The Order Entry application (orderent.exe) is part of a suite of desktop applications, written in Microsoft® Visual Basic® version 5.0, that automate business processes in a small bookstore. These applications share a common database, as described in "Designing a Simple Retail-Oriented Database: Duwamish Database, Phase 1." The Phase 1 applications are a starting point for what will be a migration, as described in "Duwamish Books," from a two-tier architecture, where each application contains its own presentation, business rule, and data access code, to a multitiered architecture, where these services are distributed across a network.
This application provides order entry and tracking functionality, including related interfaces to manage customers, employees, and suppliers. It shares user interfaces with the Point of Sale application (see "Coordinating Sales and Inventory: Duwamish Books Point of Sale, Phase 1") and the Launcher application, and borrows from the Catalog application (see "Searching the Inventory: Duwamish Catalog Application, Phase 1").
The Order Entry application manages purchase orders and sale orders. A purchase order represents a purchase of items from a supplier and a sale order represents a sale that requires shipping or other processing.
A purchase order consists of an order entity and a set of order details. It uses the following attributes of the order entity.
Table 1. Purchase Order Attributes
Attributes | Description |
PKId | Uniquely identifies the order. |
EmployeeID | Identifies the employee who entered the order. |
SupplierId | Identifies the supplier of the order items. |
OrderDate | Indicates the date/time the order was added to the database. |
Status | Indicates where the order is in its lifetime, as explained below. |
SubTotal | Indicates the retail total of all items being ordered. |
Each order detail uses the following attributes of the OrderDetail entity.
Table 2. OrderDetail Entity
Attributes | Description |
PKId | Uniquely identifies the order detail. |
OrderId | Identifies the purchase order (PKId). |
ItemId | Identifies the item being ordered. |
UnitPrice | Indicates the retail price of the item. |
Quantity | Indicates the quantity ordered. |
A purchase order has the following states during its lifetime:
A sale order represents an order that requires more processing than a simple "customer buys and takes book" sale. This type of sale may be shipped or held for the customer and may require items to be ordered. Sale orders are generated by the Point of Sale application when sale quantity exceeds quantity on hand or when the ship option is selected.
A sale order uses the following attributes of the order entity.
Table 3. Sale Order Attributes
Attributes | Description |
PKId | Uniquely identifies the order. |
CustomerID | Identifies the customer. |
EmployeeID | Identifies the employee who entered the order. |
OrderDate | Indicates the date/time the order was inserted into the database. |
PickupDate | Indicates the date/time the order was picked up by the customer. |
Status | Indicates where the order is in its lifetime. |
IsSales | TRUE indicates that the order is a sale order. |
A sale order has the following states during its lifetime:
The Order Entry user interfaces (UIs) are designed to emphasize reuse. The Order interface can represent either a purchase or a sale order. The Actor interface represents entities such as a Customer, Employee, or Supplier that I am referring to as Actors. The Actors interface represents a list of these Actors and, finally, the Orders interface represents a list of either purchase or sale orders.
I used common data classes throughout the application so that whenever my code is manipulating customers or sales it is using objects of class cCustomer or cSale rather than opening database records or maintaining other variable types to represent these entities. When I select an order from the orders list I receive an object of cOrder. These data objects make a clean application that is easy to maintain while leading naturally to a client/server architecture that does not require a constant database connection.
Business rules (such as the process that marks a sale to be shipped or that determines what sort of sales generate immediate inventory transactions) are grouped together so they can be easily changed in one place without having to make changes throughout the application. Business rules update the totals for an order object or determine that a sale exceeds stock and that a shipping order will be generated.
Finally, I use the common data access module (data_api.bas) wherever possible and I'll document where I had to add to it. I extended this application programming interface (API) where I wanted to insert parent/child records and enclose multiple database operations in an ActiveX® Data Object (ADO) transaction.
The Order Entry application is basically the thinnest possible container (orderent.exe) around a set of freestanding form modules. The forms are freestanding in that they have well-defined interfaces and code dependencies and their inner workings are largely hidden. This encapsulation allows them to be freely reused in ways unknown to the original designer whenever an application has need for or needs to work with one of the common application data entities (Customer, Employee, Supplier, Order, Order Detail, or Catalog item).
The services of a form are invoked by assigning the operating mode, presetting any Object properties, and then showing the form. When the user exits the form, resulting data such as a Customer, Employee, Item, Order, or Supplier Object is exposed as a Form property.
The Order Entry application allows store employees to enter new purchase orders and to track existing purchase and sale orders. Order entry is performed by employees in Phase 1, so little effort is made to restrict access to data, although future phases of the application will have increased security as customers are able to write their own orders from kiosks in the store and over the Internet. Before discussing implementation of the user interfaces, I'll specify them as a suite of objects with properties and methods that accomplish the required data flow. Many of the data properties described below are of classes that wrap the data entities described in "Designing a Simple Retail-Oriented Database."
Table 4. Order Entry User Interfaces
Interface | Description |
Order Interface—frmOrder | Displays a purchase or sale order and provides menu and toolbar choices to create new orders and to look up existing orders. |
Orders Interface—frmOrders | Displays a list of existing orders filtered according to operating mode. Provides menu and toolbar choices to filter the list or to select an order. |
Actors Interface—frmActors | Provides a list of Customers, Employees or Suppliers. Provides menu and toolbar choices to add or edit these entities. |
Actor Interface—frmActor | An Add/Display/Edit interface for Address, Customer, Employee, or Supplier entities. |
The Order form displays orders and provides an interface to create new purchase orders and to look up existing purchase and sale orders. The interface includes menu and toolbar selections for new and existing orders. The Order form (frmOrder) exposes the following properties and methods.
Table 5. Order Form Properties and Methods
Property/method | Description |
OrderMode | Read/Write integer property controls form mode. The available modes are described below.
IcBASE_MENU IcADD_PURCHASE_ORDER IcDISPLAY_PURCHASE_ORDER IcDISPLAY_SALES_ORDER |
Order | Read/Write object of class cOrder, as defined in common class module order.cls. |
Table 6. Order Form OrderMode = icBASE_MENU
This is the Entry interface. Base Menu mode presents the following choices.
User choices—toolbar and menu | State | Function |
File menu: Close
–or– Toolbar: Close |
Enabled | Exits orderent.exe. |
File menu: Open Purchase
–or– Toolbar: Open PO |
Enabled | Displays Order form with list of purchase orders. Changes OrderMode to icDISPLAY_PURCHASE_ORDER if an order is selected. |
File menu: Open Sales
–or– Toolbar: Open SO |
Enabled | Displays Orders form with list of sale orders. Changes OrderMode to icDISPLAY_SALES_ORDER if an order is selected. |
File menu: New Purchase
–or– Toolbar: New PO |
Enabled | Changes OrderMode to icADD_PURCHASE_ORDER. |
Table 7. Order Form OrderMode = icADD_PURCHASE_ORDER
Add Purchase Order mode is for entering a purchase order. It presents the following choices.
User choices | State | Function |
File menu: Cancel
–or– Toolbar: Cancel |
Enabled | Cancels order entry and returns to Base Menu mode. |
Order menu: Supplier
–or– Toolbar: Supplier |
Enabled before supplier is selected | Selects supplier for order (put Actors interface into GetSupplier mode, display Actors interface, get supplier from Actors form). |
Catalog menu: Search
–or– Toolbar: Catalog |
Enabled once supplier is selected | Adds an item to order (put Search interface into GetItemMode mode, set Search interface to Supplier, display Search interface, get selected item(s) and quantities supplier from SearchDetail form). |
Catalog menu: Remove
–or– Toolbar: Remove |
Enabled if an order detail item is selected | Deletes selected order detail item. |
Submit | Enabled if order is not empty | Inserts order and order details into database. If transaction is successful, change OrderMode to icDISPLAY_PURCHASE_ORDER and display order. |
Table 8. Order Form OrderMode = icDISPLAY_PURCHASE_ORDER
User choices | State | Function |
Done | Enabled | Returns to Base Menu mode. |
Orders | Enabled | Selects an existing purchase order (put Orders interface in ExistingPurchase mode, display Orders, get order from Orders form, display selected order). |
Orders Form OrderMode = icDISPLAY_PURCHASE_ORDER
Similar to icDISPLAY_PURCHASE_ORDER.
The Orders form provides a way to select an existing order from a list. The list of purchase orders may be filtered according to supplier and sale orders may be filtered by customer. The Orders form (frmOrders) exposes the following properties and methods.
Table 9. Orders Form Properties and Methods
Property/method | Description |
OrdersMode | Read/Write integer property controls form mode.
icPURCHASE_ORDERS displays list of purchase orders. icSALES_ORDERS displays list of sale orders. |
Order | Read/Write object of class cOrder, as defined in common class module order.cls. |
Customer | Read/Write object of class cCustomer, as defined in common class module customer.cls. References customer of selected sale order. Setting this property filters sale orders list to those of the customer. |
Employee | Read/Write object of class cEmployee, as defined in common class module employee.cls. References employee associated with selected purchase order. |
Supplier | Read/Write object of class cContact, as defined in common class module contact.cls. References supplier of selected purchase order. Setting this property filters list of purchase orders to those of the supplier. |
Table 10. Orders Form OrdersMode = icPURCHASE_ORDERS
Displays a list of purchase orders filtered by supplier.
User choices | State | Function |
Toolbar: Cancel | Enabled | Cancels order selection. Sets Order, Customer, and Supplier properties to Nothing and hides form. |
Toolbar: Supplier | Enabled | Selects a supplier to filter list of orders. Uses services of frmActors. |
Toolbar: Select
–or– List: Double-click |
Enabled when an order is selected in list | Selects highlighted order from list and hides form, returning control to client code. Sets Order, Employee, and Supplier properties corresponding to selected purchase order. |
Table 11. Orders Form OrdersMode = icSALES_ORDERS
Displays a list of sale orders filtered by customer.
User choices | State | Function |
Toolbar: Cancel | Enabled | Cancels order selection. Sets Order property to Nothing and hides form, returning control to client code. |
Toolbar: Customer | Enabled | Selects a customer to filter list of orders. Uses services of frmActors. |
Toolbar: Select
–or– List: Double-click |
Enabled when an order is selected in list | Selects highlighted order from list and hides form, returning control to client code. Sets Order, Customer, and Employee properties according to selected sale order. |
The Actors form provides a way to select a Customer, Employee, or Supplier from a list or to Add/Edit these entities. The Actors form (frmActors) exposes the following properties and methods.
Table 12. Actors Form Properties and Methods
Property/method | Description |
ActorsMode | Read/Write integer property controls form mode. The available modes are described below.
icGET_CUSTOMER icGET_SUPPLIER icGET_EMPLOYEE |
Customer | Read/Write object of class cCustomer, as defined in common class module ccustomer.cls. Is selected customer. |
Employee | Read/Write object of class cEmployee, as defined in common class module cemployee.cls. Is selected employee. |
Supplier | Read/Write object of class cContact, as defined in common class module ccontact.cls. Is selected supplier. |
Table 13. Actors Form ActorsMode = icGET_CUSTOMER, icGET_SUPPLIER or icGET_EMPLOYEE
User choices | State | Function |
Toolbar: Cancel | Enabled | Cancels selection. Sets Customer, Employee, or Supplier property to Nothing (according to mode) and hides form, returning control to client code. |
Toolbar: Edit | Enabled when a list item is highlighted | Edits selected entity using services of frmActor. |
Toolbar: New | Enabled | Adds a new entity using services of frmActor. |
Toolbar: Select
–or– List: Double-click |
Enabled when a list item is highlighted | Selects highlighted Customer, Employee, or Supplier from list and hides form, returning control to client code. Sets Customer, Employee, or Supplier property to selected entity. |
The Actor form provides a way to add, display, or edit a Customer, Employee, Publisher, Shipping Address, or Supplier.
The Actor form (frmActor) exposes the following properties and methods.
Table 14. Actor Form Properties and Methods
Property/method | Description |
ActorMode | Read/Write integer property controls form mode. The available modes are described below.
IcADD_ADDRESS IcDISPLAY_ADDRESS IcEDIT_ADDRESS IcADD_CUSTOMER IcDISPLAY_CUSTOMER icEDIT_CUSTOMER icADD_EMPLOYEE icDISPLAY_EMPLOYEE icEDIT_EMPLOYEE icADD_SUPPLIER icDISPLAY_SUPPLIER icEDIT_SUPPLIER
|
Address | Read/Write object of class cAddress, as defined in comon class module address.cls. |
Customer | Read/Write object of class cCustomer, as defined in common class module customer.cls. |
Employee | Read/Write object of class cEmployee, as defined in common class module employee.cls. |
Supplier | Read/Write object of class cContact, as defined in common class module contact.cls. |
Table 15. Actor Form ActorMode = icADD_ADDRESS, icADD_CUSTOMER, icADD_EMPLOYEE or icADD_SUPPLIER
User choices | State | Function |
Toolbar: Cancel | Enabled | Cancels transaction. Sets Address, Customer, Employee, or Supplier property to Nothing and hides form, returning control to client code. |
Add | Enabled | Executes transaction. Inserts a Customer, Employee, or Supplier in database, sets Customer, Employee or Supplier object to new entity, and hides form if successful. Displays error message and returns control to user if not successful.
In Add Address mode, sets Address object to new address. |
Table 16. Actor Form ActorMode icDISPLAY_ADDRESS, icDISPLAY_CUSTOMER, icDISPLAY_EMPLOYEE or icDISPLAY_SUPPLIER
User choices | State | Function |
Toolbar: Done | Enabled | Hides form, returning control to client code. |
Table 17. Actor form ActorMode = icUPDATE_CUSTOMER, icUPDATE_EMPLOYEE or icUPDATE_SUPPLIER
Similar to Add modes with update choice instead of add. |
Now, with the data flow and the user interface specified, I'll give a quick code demo showing how well the UI components work together. After I show the components working together, I'll drill down into more detail.
The following code segment shows how to use the Orders form to select an existing purchase order and to get data objects representing the selected order, the employee who created the purchase order, and the supplier:
' Configure orders form to list purchase orders
frmOrders.OrdersMode = icPURCHASE_ORDERS
Set frmOrders.Order = m_oOrder
' Show orders form as a modal window
frmOrders.Show vbModal
' Get selected order with employee and supplier objects
Set m_oOrder = frmOrders.Order
Set m_oEmployee = frmOrders.Employee
Set m_oSupplier = frmOrders.Supplier
In a similar way, the following code segment shows how to use the Actors form to select a customer:
' Configure actors form to list customers
frmActors.ActorsMode = icGET_CUSTOMER
Set frmActors.Customer = m_oCustomer
' Shows actors form as a modal window
frmActors.Show vbModal
' Get selected customer object
Set m_oCustomer = frmActors.Customer
Now I'll use the services of multiple forms to create a purchase order. The following sequence could be taken from the Order form New Purchase choice but, to show how the services of freestanding forms can be used in ways not anticipated by the original designer, I'll use the new order services directly from a piece of client code.
frmOrder.Mode = NewPurchaseOrder
frmOrder.Show vbModal
frmActors.Mode = GetSupplier
frmActors.Show vbModal
Set oNewSupplier = frmActors.Supplier
' Clear order items if another supplier was previously selected
If Not oNewSupplier Is Nothing Then
If Not oSupplier Is Nothing Then
If oNewSupplier.PKID <> oSuplier.PKId Then
' Supplier has changed so clear items in list
lvwItems.Items.Clear
Set cItemDetails = New clsItemDetails
End IF
End If
End if
' Have search form cache an 'order' of item/quantity pairs.
frmSearch.SetOrderable True
' Clear previous 'order'
Set frmSearch.Orders = Nothing
' Filter catalog items by selected supplier
frmSearch.SetSupplierID m_oSupplier.PKId
' Show the form modally
frmSearch.Show vbModal
' Add items that were selected to the order details collection
If CBool(frmSearch.GetOrderCount) Then
For i = 1 To frmSearch.GetOrderCount
Set oOrderDet = frmSearch.GetOrder(i)
' Use a temporary ID to be replaced by db autonumber
oOrderDet.PKId = NextDetID
sKey = OrderCommon.IDToKey(oOrderDet.PKId)
' Add to collection
m_cOrderDetails.Add oOrderDet, sKey
Set oOrderDet = Nothing
Next
End If
' Business rules for change in order
SumOrder
' Refresh detail list
FillListView
' Refresh form controls
UpdateUI
The following code passes the order to the business rules that validate the order and attempt to insert it into the database. If successful, the interface is returned to Base Menu mode and refreshed. If the order is not complete or does not pass validation, the appropriate error message is announced and control is returned to the user:
' Submit order to database
If MsgBox(scCONFIRM TO_SUBMIT, vbYesNo) = vbYes Then
' Hand order to business rules governing submit
If SubmitOrder Then
MsgBox (scSUBMIT_SUCCESS)
' Place form in base menu mode and refresh
m_iOrderMode = ORDER_MODE.icBASE_MENU
' Configure form controls
ConfigureForm
' Refresh detail list
FillListView
' UpdateUI
UpdateUI
End If
End If
Where will the architecture that I implemented here need revision as the bookstore grows along the scenarios outlined in the "An Introduction to the Duwamish Books Sample" article? As covered in the overview, data access will first be encapsulated into an API that supports all of the transactions that have been identified here. This will make the applications easier to maintain, because all data access code is in one place, and it will provide other benefits such as better data security and scalability. The data access code will need revisions.
First, let's note some of the good points of this design:
Although the design does have these good points, the migration to Phase 2 will address one design weakness: Changes to data access technology may require that all the applications be modified, recompiled, and reinstalled. By putting data access code in a separate component, data access changes can be made without changing the client application.
Beyond Phase 2, business rules will be broken out so that they can be revised without rebuilding the application and, finally, the user interfaces identified here will be refined so that they work well in a distributed environment and become language-independent.