Difference between revisions of "VbzCart/tables"
(→cat_supp: +Notes field) |
(→Catalog: tables extracted to separate pages) |
||
Line 14: | Line 14: | ||
In the previous version of the cart software, we had to have items of somewhat different appearance (e.g. longsleeve and shortsleeve shirts) sharing a single title, so as to remove the necessity to ''always'' have pictures for each. In this version, a title can point to another title for its picture, thus keeping it clear whether the picture is truly representative or just an approximation. | In the previous version of the cart software, we had to have items of somewhat different appearance (e.g. longsleeve and shortsleeve shirts) sharing a single title, so as to remove the necessity to ''always'' have pictures for each. In this version, a title can point to another title for its picture, thus keeping it clear whether the picture is truly representative or just an approximation. | ||
− | + | * [[/cat_supp]]: catalog suppliers (i.e. manufacturers, wholesalers) | |
− | catalog suppliers (i.e. manufacturers, wholesalers) | + | * [[/cat_depts]]: catalog departments within a supplier |
− | + | * [[/cat_titles]]: titles within a department - a particular "thing" which may be available in multiple varieties | |
− | + | * [[/cat_items]]: items within a title - a particular version of a title | |
− | + | * [[/cat_ittyps]]: item types - every item has one, but they are often all the same | |
− | + | * [[/cat_ioptns]]: these typically distinguish items within a title | |
− | + | * [[/cat_images]]: image data; so far, we only keep track of images by title | |
− | + | * [[/cat_ship_costs]]: shipping costs for different items | |
− | + | * [[/cat_pages]]: mapping URLs to various catalog entities (suppliers, depts, titles) | |
− | + | * [[/cat_pages_old]]: catalog designations sometimes change; where possible, this lets us redirect old URLs | |
− | + | ====future==== | |
− | + | It may be that Departments and Suppliers should be handled by the Topics tree, but this involves creating some infrastructure which is going to take some doing. (Specifically, we need to be able to assign named values – like "catkey" – to any topic, so that each Department topic and Supplier topic can refer to the proper catalog number.) | |
− | + | ====leftover stuff==== | |
− | + | Are these still being used? To be checked. 2008-12-06 | |
− | * | ||
− | * | ||
− | * | ||
− | * | ||
− | * | ||
− | |||
− | ==== | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | ==== | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
SQL for updating availability from current stock: | SQL for updating availability from current stock: | ||
Line 130: | Line 45: | ||
<section end=sql /> | <section end=sql /> | ||
− | + | <section begin=sql /><mysql>CREATE OR REPLACE VIEW _cat_pages AS | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | <section begin=sql /><mysql> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | CREATE OR REPLACE VIEW _cat_pages AS | ||
SELECT | SELECT | ||
CONCAT_WS('-','S',ID) AS AB, | CONCAT_WS('-','S',ID) AS AB, | ||
Line 264: | Line 73: | ||
i.ID, | i.ID, | ||
'I' AS Type | 'I' AS Type | ||
− | FROM cat_images AS i LEFT JOIN qryCat_Titles AS t ON i.ID_Title=t.ID WHERE (i.isActive) AND (i.Ab_Size NOT IN ('th','sm')); | + | FROM cat_images AS i LEFT JOIN qryCat_Titles AS t ON i.ID_Title=t.ID WHERE (i.isActive) AND (i.Ab_Size NOT IN ('th','sm'));</mysql> |
+ | <section end=sql /> | ||
− | CREATE PROCEDURE Upd_CatPages() | + | <section begin=sql /><mysql>CREATE PROCEDURE Upd_CatPages() |
REPLACE INTO cat_pages | REPLACE INTO cat_pages | ||
SELECT * FROM _cat_pages | SELECT * FROM _cat_pages |
Revision as of 16:00, 6 December 2008
{{#lst:VbzCart|navbar}}: tables
Subpages
- /customer tables
Tables
- "#" indicates Primary Key fields
- "@" indicates autonumbered fields
Catalog
These tables describe the items available for sale and correctly describe and price items added to the shopping cart.
A "Title" is a group of items with a common description, e.g. different sizes or styles of a shirt, different media (CD, cassette) for an audio recording.
In the previous version of the cart software, we had to have items of somewhat different appearance (e.g. longsleeve and shortsleeve shirts) sharing a single title, so as to remove the necessity to always have pictures for each. In this version, a title can point to another title for its picture, thus keeping it clear whether the picture is truly representative or just an approximation.
- /cat_supp: catalog suppliers (i.e. manufacturers, wholesalers)
- /cat_depts: catalog departments within a supplier
- /cat_titles: titles within a department - a particular "thing" which may be available in multiple varieties
- /cat_items: items within a title - a particular version of a title
- /cat_ittyps: item types - every item has one, but they are often all the same
- /cat_ioptns: these typically distinguish items within a title
- /cat_images: image data; so far, we only keep track of images by title
- /cat_ship_costs: shipping costs for different items
- /cat_pages: mapping URLs to various catalog entities (suppliers, depts, titles)
- /cat_pages_old: catalog designations sometimes change; where possible, this lets us redirect old URLs
future
It may be that Departments and Suppliers should be handled by the Topics tree, but this involves creating some infrastructure which is going to take some doing. (Specifically, we need to be able to assign named values – like "catkey" – to any topic, so that each Department topic and Supplier topic can refer to the proper catalog number.)
leftover stuff
Are these still being used? To be checked. 2008-12-06
SQL for updating availability from current stock:
<mysql> CREATE PROCEDURE Upd_CatItems_fr_StkItems_StkBins()
UPDATE cat_items AS i LEFT JOIN ( SELECT si.ID_Item, SUM(si.Qty) AS QtyInStock FROM stk_items AS si LEFT JOIN stk_bins AS sb ON si.ID_Bin=sb.ID WHERE (sb.isForSale) AND ISNULL(sb.WhenVoided) AND ISNULL(si.WhenRemoved) GROUP BY ID_Item ) AS sig ON i.ID=sig.ID_Item SET i.QtyInStock = sig.QtyInStock, i.isForSale = i.isInPrint OR (sig.QtyInStock > 0);</mysql>
<mysql>CREATE OR REPLACE VIEW _cat_pages AS
SELECT CONCAT_WS('-','S',ID) AS AB, LOWER(CatKey) AS Path, ID, 'S' AS Type FROM cat_supp UNION SELECT CONCAT_WS('-','D',ID) AS AB, LOWER(CatWeb_Dept) AS Path, ID, 'D' AS Type FROM qryCat_Depts UNION SELECT CONCAT_WS('-','T',ID) AS AB, REPLACE(LOWER(CatWeb),'-','/') AS Path, ID, 'T' AS Type FROM qryCat_Titles UNION /* image pages (every size except thumbnail and small) */ SELECT CONCAT_WS('-','I',i.ID) AS AB, LOWER(CONCAT_WS('/',REPLACE(t.CatWeb,'-','/'),AttrFldr,i.Ab_Size)) AS Path, i.ID, 'I' AS Type FROM cat_images AS i LEFT JOIN qryCat_Titles AS t ON i.ID_Title=t.ID WHERE (i.isActive) AND (i.Ab_Size NOT IN ('th','sm'));</mysql>
<mysql>CREATE PROCEDURE Upd_CatPages()
REPLACE INTO cat_pages SELECT * FROM _cat_pages ORDER BY Path;</mysql>
This --
- LOWER(CONCAT_WS('/',REPLACE(t.CatWeb,'-','/'),AttrFldr,i.Ab_Size)) AS Path,
...is intended to be a simplified version of this:
- LOWER(CONCAT(REPLACE(t.CatWeb,'-','/'),'/',CONCAT_WS('/',AttrFldr,i.Ab_Size))) AS Path,
(saved here in case the simplification doesn't work)
Catalog Entry
These are tables used to make it easier to update the catalog. The key concept here is "catalog title groups" (CTGs). A CTG is a set of titles which are all available with the same set of options (e.g. sizes) at the same prices (e.g. $10 S, $11 M-XL, $12 2XL). The final vbz catalog (cat_items) is the result of a sort of vector multiplication of Titles (cat_titles) and CTGs plus any items in stock.
A "source" is usually a printed catalog from a supplier, though it can also be the manufacturer's web site on a particular date.
See VbzCart catalog building for queries and processes.
ctg_supp
Information about suppliers which isn't needed for customer web site
<mysql>CREATE TABLE `ctg_supp` (
ID INT NOT NULL COMMENT "cat_supp.ID - supplier to which information applies", ID_PriceFunc INT DEFAULT NULL COMMENT "ctg_prc_funcs.ID - function to use when calculating prices for this supplier", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
ctg_prc_funcs
Functions for calculating our retail price from the wholesale cost we pay:
- retail = {(wholesale * PriceFactor) + PriceAddend} rounded up to next PriceRound
<mysql>CREATE TABLE `ctg_prc_funcs` (
ID INT NOT NULL AUTO_INCREMENT, Name VARCHAR(63) NOT NULL COMMENT "descriptive name for this price function", PriceFactor DECIMAL(9,2) COMMENT "amount by which to multiple wholesale cost", PriceAddend DECIMAL(9,2) COMMENT "amount by which to pad wholesale cost", PriceRound DECIMAL(9,2) COMMENT "round-up-to-next this amount", Notes VARCHAR(255) COMMENT "usage notes etc.", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
ctg_sources
<mysql>CREATE TABLE `ctg_sources` (
ID INT NOT NULL AUTO_INCREMENT, Name VARCHAR(63) NOT NULL COMMENT "name of source", Abbr VARCHAR(15) NOT NULL COMMENT "abbreviation, for drop-down lists", ID_Supplier INT NOT NULL COMMENT "Suppliers.ID - supplier for this source", DateAvail DATETIME DEFAULT NULL COMMENT "earliest date on which this source is valid", ID_Supercede INT DEFAULT NULL COMMENT "[Supplier Catalogs].ID of catalog which supercedes this one (NULL = this one is current)", isCloseOut BOOL DEFAULT FALSE COMMENT "TRUE = this catalog is a list of discontinued items no longer in print (closeouts); FALSE = normal catalog", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
- To discontinue a supplier, set ID_Supercede=ID for all active sources.
ctg_groups
Master list of groups (CTGs)
<mysql>CREATE TABLE `ctg_groups` (
ID INT NOT NULL AUTO_INCREMENT, ID_Supplier INT DEFAULT NULL COMMENT "Suppliers.ID -- supplier to which this group applies (NULL = no fixed supplier)", Name VARCHAR(63) DEFAULT NULL COMMENT "Name of this group (brief)", Descr VARCHAR(127) DEFAULT NULL COMMENT "Description (for display; if NULL, use ItemType fields)", isActive BOOL DEFAULT FALSE, Code VARCHAR(7) DEFAULT NULL COMMENT "suffix for Items.CatNum", Sort VARCHAR(31) DEFAULT NULL COMMENT "sorting key within group list", ID_Redir INT DEFAULT NULL COMMENT "TGroups.ID -- if not null, redirect all references to the current ID to use ID_Redir instead",
PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
ctg_titles
Specifies which groups a title belongs to
<mysql>CREATE TABLE `ctg_titles` (
ID INT NOT NULL AUTO_INCREMENT, ID_Title INT DEFAULT NULL COMMENT "cat_titles.ID", ID_Group INT DEFAULT NULL COMMENT "ctg_groups.ID", ID_Source INT DEFAULT NULL COMMENT "ctg_sources.ID - source which enables this selection", WhenDiscont DATETIME DEFAULT NULL COMMENT "non-sourced discontinuation", GroupCode VARCHAR(7) DEFAULT NULL COMMENT "catalog code extension for this title group (optional)", GroupDescr VARCHAR(127) DEFAULT NULL COMMENT "description extension for this title group (required if GroupCode is used)", GroupSort VARCHAR(7) DEFAULT NULL COMMENT "sorting override", isActive BOOL DEFAULT FALSE COMMENT "this group-title membership is active?", Supp_CatNum VARCHAR(15) DEFAULT NULL COMMENT "catalog # for restock from supplier", Notes VARCHAR(255) DEFAULT NULL COMMENT "notes about this particular title's availability in this group", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
ctg_items
Specifies which item varieties/prices are available in a group
<mysql>CREATE TABLE `ctg_items` (
ID INT NOT NULL AUTO_INCREMENT, ID_Group INT NOT NULL COMMENT "ctg_groups.ID", ID_ItTyp INT NOT NULL COMMENT "cat_ittyps.ID -- basic item type", ID_ItOpt INT DEFAULT NULL COMMENT "cat_itopts.ID -- option for this item", Descr VARCHAR(63) COMMENT "name or description for this item/option", PriceBuy DECIMAL(9,2) DEFAULT NULL COMMENT "cost to us", PriceSell DECIMAL(9,2) DEFAULT NULL COMMENT "our price to retail customer", PriceList DECIMAL(9,2) DEFAULT NULL COMMENT "supplier's suggested retail price", ID_ShipCost INT DEFAULT NULL COMMENT "cat_ship_costs.ID", isActive BOOL DEFAULT FALSE COMMENT "this group/type/option is available", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
ctg_updates
There may be a better way to do this, but haven't been able to figure it out yet. This table keeps track of the correspondence between {items as generated from the catalog title group data} and {items in cat_items}. Off the top of my head, I can't remember why we need to keep more fields than just ID_Item and IDS_Item, but I did look at this table very closely awhile back and ended up with all those extra fields still in it. TO DO: document why they're needed.
<mysql>CREATE TABLE `ctg_updates` (
ID_Item INT NOT NULL AUTO_INCREMENT COMMENT "cat_items.ID", IDS_Item VARCHAR(63) NOT NULL UNIQUE COMMENT "cat_titles.ID + CatSfx", isActive BOOL NOT NULL DEFAULT FALSE COMMENT "current sources do generate a record for this item", CatSfx VARCHAR(31) DEFAULT NULL COMMENT "suffix which uniquely identifies this item's genome, so to speak, within all items for the same title", CatNum VARCHAR(63) NOT NULL COMMENT "CatNum = Title.CatNum + CatSfx", ID_Title INT DEFAULT NULL, ID_CTG_Group INT DEFAULT NULL, ID_CTG_Title INT DEFAULT NULL, ID_CTG_Item INT DEFAULT NULL, ID_ItTyp INT DEFAULT NULL COMMENT "needed for join with cat_items, even though it comes from ID_CTG_Item", ID_ItOpt INT DEFAULT NULL COMMENT "needed for join with cat_items, even though it comes from ID_CTG_Item", PRIMARY KEY(`IDS_Item`), INDEX(`ID_Item`), INDEX(`CatNum`)
) ENGINE = MYISAM;</mysql>
- isActive is necessary because otherwise there was no way to convey that a formerly-existing item is no longer active; while it should be possible to pull this information back out from qryCtg_Items_updates_joinable when updating cat_items from ctg_updates, the necessary join (showing all items in cat_items not found in qryCtg_Items_updates_joinable) seems to take a prohibitively long time to run. Perhaps this can be optimized later, which would also eliminate the need for most of the fields in ctg_updates.
ctg_upd1
Experimental, but hopefully will replace ctg_updates which never worked entirely properly. This table contains only the essential fields from qryCtg_src.
<mysql>CREATE TABLE `ctg_upd1` (
CatSfx VARCHAR(31), isCloseOut BOOL, ID_CTG_Title INT, ID_CTG_Item INT, ID_Title INT, ID_ItTyp INT, ID_ItOpt INT, ID_ShipCost INT, PriceBuy DECIMAL(9,2), PriceSell DECIMAL(9,2), PriceList DECIMAL(9,2), ItOpt_Descr_part VARCHAR(63), NameSng VARCHAR(63), GrpItmDescr VARCHAR(63), TitleGroupDescr VARCHAR(63), OptionDescr VARCHAR(63), ItOpt_Sort VARCHAR(31), GrpCode VARCHAR(15), GrpDescr VARCHAR(31), GrpSort VARCHAR(31), IDS_Item VARCHAR(31), CatNum VARCHAR(31), ItOpt_Descr VARCHAR(63), PRIMARY KEY(`IDS_Item`)
) ENGINE = MYISAM;</mysql>
ctg_upd2
Experimental, but hopefully will replace ctg_updates which never worked entirely properly.
<mysql>CREATE TABLE `ctg_upd2` (
ID_Item INT DEFAULT NULL COMMENT "cat_items.ID", IDS_Item VARCHAR(63) NOT NULL UNIQUE COMMENT "cat_titles.ID + CatSfx", isForSale BOOL DEFAULT FALSE COMMENT "to be recalculated (assume false, then set true if available or in stock)", QtyInStock INT DEFAULT NULL COMMENT "to be recalculated", Supp_CatNum VARCHAR(32) COMMENT "preserved from cat_items", QtyMin_Stk INT DEFAULT NULL COMMENT "preserved from cat_items", cntDups INT DEFAULT 0, PRIMARY KEY(`IDS_Item`), UNIQUE KEY(`ID_Item`)
) ENGINE = MYISAM;</mysql>
Ordering
The items a customer wants to order are saved in a cart. The cart also saves session information, e.g. customer's IP/domain, but not the customer's shipping or payment data. Later on, we'll allow customers to make changes to carts after the cart has already been assigned to an order, so will need session info stored separately, which is why it's in a separate table. See VbzCart ordering for details.
There is a handful of tables just for managing customer contact information, so I'm putting those on a separate page: VbzCart customer tables
core_orders
- History:
- 2008-11-20 Changing field names:
- WhenOpened -> WhenImported (this won't be set once order data is kept live)
- WhenClosed -> WhenClosed_old (we want "WhenClosed" to be a timestamp for when the order was finished, and we don't know what this legacy field was actually for)
- WhenUpdated -> WhenUpdated_old (no longer being used)
- 2008-11-20 Changing field names:
- Notes:
- Normal sorting order is: CONCAT(IFNULL(SortPfx,''),Number)
- The fields have to be concatenated, else the non-NULL SortPfx records always get sorted after the rest. "IFNULL(SortPfx,Number),Number" also works; not sure which is computationally cheaper.
- The customer contact information schema needs to be significantly reworked:
- Orders should save a string of the actual name used
- There should be some kind of "customer" record which contains the current defaults, as well as alternatives, for names & contact info
- Normal sorting order is: CONCAT(IFNULL(SortPfx,''),Number)
- There should eventually be an event log for orders so a complete activity history can be pulled up, which may render some of the When* fields obsolete.
- If not dealing with legacy data, WhenStarted should be NOT NULL.
- As implemented for vbz, there are actually some additional fields for storing legacy data until we figure out what to do with it:
- WhenClosed_old: it's not clear what this field actually did
- WhenUpdated_old: this was originally supposed to be set if the order was changed before being imported, but it doesn't seem to have been used since 2001-02-09. This information would be better stored in a log file anyhow.
<mysql>CREATE TABLE `core_orders` (
`ID` INT NOT NULL AUTO_INCREMENT, `Number` VARCHAR(63) NOT NULL COMMENT "order number for human reference; generated by web site", `SortPfx` VARCHAR(7) DEFAULT NULL COMMENT "sorting prefix -- earlier prefix schemes sort wrong, and this overrides that", `PassCode` VARCHAR(15) DEFAULT NULL COMMENT "order's security passcode - useful if user has not created an account", `ID_Pull` INT DEFAULT NULL COMMENT "ord_pulls.ID: ID of the currently active Pull for this order (NULL = order is active)", `ID_NameBuyer` INT DEFAULT NULL COMMENT "cust_names.ID -- purchaser, contact for discussing order", `ID_NameRecip` INT DEFAULT NULL COMMENT "cust_names.ID -- shipping destination", `ID_ContactAddrRecip` INT DEFAULT NULL COMMENT "cust_addrs.ID -- shipping address to use (NULL = recipient's default address)", `ID_ContactPhone` INT DEFAULT NULL COMMENT "cust_phones.ID -- phone number for this order", `ID_ContactEmail` INT DEFAULT NULL COMMENT "cust_emails.ID -- email address for this order", `PayType` INT DEFAULT NULL COMMENT "aux_pay_types.code -- payment method used for this order", `ID_ChargeCard` INT DEFAULT NULL COMMENT "cust_cards.ID -- bank card used to pay for this order", `WebTotal_Merch` DECIMAL(9,2) DEFAULT NULL COMMENT "total amount quoted for merchandise", `WebTotal_Ship` DECIMAL(9,2) DEFAULT NULL COMMENT "total quoted for additional charges (S/H, tax)", `WebTotal_Final` DECIMAL(9,2) DEFAULT NULL COMMENT "final total charge quoted", `WhenStarted` DATETIME DEFAULT NULL COMMENT "when order record was created", `WhenImported` DATETIME DEFAULT NULL COMMENT "when entered (downloaded) to database (or entered manually) and ready for processing", `WhenEdited` DATETIME DEFAULT NULL COMMENT "when order was last edited in-store", `WhenNeeded` DATETIME DEFAULT NULL COMMENT "when customer needs to receive order (NULL = no deadline)", `WhenClosed` DATETIME DEFAULT NULL COMMENT "when order was completed -- all items shipped, cancelled, or unavailable", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
ord_lines
<mysql>CREATE TABLE `ord_lines` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Order` INT NOT NULL COMMENT "core_orders.ID", `Seq` INT DEFAULT NULL COMMENT "sequence in which items were added to order", `ID_Item` INT NOT NULL COMMENT "cat_item.ID", `CatNum` VARCHAR(63) DEFAULT NULL COMMENT "catalog number as ordered", `Descr` VARCHAR(255) DEFAULT NULL COMMENT "description as ordered", `QtyOrd` INT DEFAULT NULL COMMENT "quantity ordered", `Price` DECIMAL(9,2) DEFAULT NULL COMMENT "price quoted for this item", `ShipPkg` DECIMAL(9,2) DEFAULT NULL COMMENT "per-package shipping quoted", `ShipItm` DECIMAL(9,2) DEFAULT NULL COMMENT "per-item shipping quoted", `isShipEst` BOOL DEFAULT FALSE COMMENT "TRUE = shipping charge is an estimate", `WhenAdded` DATETIME DEFAULT NULL COMMENT "when this item was added to the order", `Notes` VARCHAR(255) DEFAULT NULL COMMENT "admin-added notes", `VbzUser` VARCHAR(127) COMMENT "VbzCart username, for when we have a user system", `Machine` VARCHAR(64) COMMENT "network name (or IP address) of machine from which the event was initiated, if applicable", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
- For installations without legacy data to accomodate, the following fields should probably be NOT NULL: CatNum, Descr, QtyOrd, Price, ShipPkg, ShipItem, WhenAdded
- The whole isShipEst thing is awkward and not very useful. We might just want to ditch it and state that all shipping charges are subject to revision but we will wait for customer approval if the shipping charge is over the price quoted.
ord_pkgs
<mysql>CREATE TABLE `ord_pkgs` (
`ID` INT NOT NULL AUTO_INCREMENT, `Seq` INT COMMENT "number used to make user-friendly pkg #", `ID_Order` INT NOT NULL COMMENT "core_orders.ID", `WhenStarted` DATETIME COMMENT "when package record was created", `WhenFinished` DATETIME, /* need to check code for actual purpose of field */ `WhenChecked` DATETIME, /* need to check code for actual purpose of field */ `WhenVoided` DATETIME COMMENT "when package was emptied; can be reused later", `isReturn` BOOL COMMENT "TRUE = this package is being returned, not shipped out", `ID_Shipment` INT COMMENT "core_shipments.ID", `ShipCost` DECIMAL(9,2) COMMENT "cost of shipping (postage)", `PkgCost` DECIMAL(9,2) COMMENT "cost of packaging / insurance", `ShipPounds` FLOAT COMMENT "shipping weight in pounds (whole or fractional)", `ShipOunces` FLOAT COMMENT "shipping weight in ounces, less ShipPounds", `ShipNotes` VARCHAR(255) COMMENT "human-entered notes about this pkg", `ShipTracking` VARCHAR(127) COMMENT "shipper's tracking number for pkg", `WhenArrived` DATETIME COMMENT "when arrived at customer, if known", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
- The field "ArrivalNotes" should be added to the message log; leaving it out of the migration for this table.
- WhenVoided: We're not deleting packages anymore. Instead, we mark an emptied, unsent package as "VOID", and log it in the container record. Voided package records can be re-used if they aren't assigned to a closed shipment; log the un-voiding prior to re-use. (Should there be a "WhenLocked" field to represent the shipment being closed? Maybe WhenFinished does this...) A package should never be voided without first removing all line items.
ord_pkg_lines
<mysql>CREATE TABLE `ord_pkg_lines` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Pkg` INT NOT NULL COMMENT "ord_pkgs.ID", `ID_OrdLine` INT COMMENT "ord_lines.ID", `ID_Item` INT NOT NULL COMMENT "cat_item", `QtyShipped` INT COMMENT "quantity shipped/charged in this package", `QtyExtra` INT COMMENT "quantity tossed in as freebies in this pkg", `QtyKilled` INT COMMENT "quantity fulfilled by being cancelled", `QtyNotAvail` INT COMMENT "quantity which can't be filled", `QtyFromStock` INT COMMENT "quantity which was moved from stock", `Notes` VARCHAR(255), PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
- QtyFromStock may be an obsolete field, as all package quantities should now come from stock.
ord_event_types
<mysql>CREATE TABLE `ord_event_types` (
`ID` INT NOT NULL AUTO_INCREMENT, `Display` VARCHAR(63) COMMENT "brief name for this event type", `Descr` VARCHAR(127) COMMENT "longer description of usage", `isActive` BOOL COMMENT "TRUE = order is open or on hold",
/* these fields all have 3 possible values:
0 = no change -1 = FORBID / TURN OFF +1 = ALLOW / TURN ON
- /
`doRestock` TINYINT COMMENT "allow/forbid restocking this order's items", `doCharge` TINYINT COMMENT "allow/forbid charging bank card for this order", `doContact` TINYINT COMMENT "turn on/off need-to-contact flag for this order", `doExamine` TINYINT COMMENT "turn on/off need-to-examine flag for this order", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
ord_events
<mysql>CREATE TABLE `ord_events` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Ord` INT NOT NULL COMMENT "core_orders.ID", `ID_Type` INT NOT NULL COMMENT "ord_event_types.ID", `WhenDone` DATETIME COMMENT "when the event happened", `Notes` VARCHAR(255) COMMENT "human-entered notes, if needed", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
Restocking
- /rstk_req - restock request master record (was /core_restock_new)
- /rstk_req_item - catalog item in a restock request
- /rstk_ord_line - list of customer orders needing those items (distribution)
- /rstk_rcd - restock shipment received
- /rstk_rcd_line - items received in a restock shipment
- /rstk_req_item_minima - items types with minimum supplier orders
obsolete tables
- /core_restocks - OBSOLETE
- /rstk_lines - OBSOLETE
Shopping
These tables store data generated by the user during the shopping process.
shop_carts
Shopping cart data is kept separate from order data because we end up with a lot of carts that never become orders; eventually they get cleaned out. Order data may eventually get cleaned out too, but with different criteria; for now, we are keeping order data indefinitely.
<mysql>DROP TABLE IF EXISTS `shop_carts`; CREATE TABLE `shop_carts` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Session` INT DEFAULT NULL COMMENT "shop_sessions.ID, when implemented -- session in which cart was created", `WhenCreated` DATETIME NOT NULL COMMENT "when the cart was first created", `WhenViewed` DATETIME DEFAULT NULL COMMENT "when the cart's contents were last displayed by the customer", `WhenUpdated` DATETIME DEFAULT NULL COMMENT "when the cart's contents were last changed", `WhenOrdered` DATETIME DEFAULT NULL COMMENT "when the cart's contents were transferred to an order", `ID_Order` INT DEFAULT NULL COMMENT "core_orders.ID of order into which cart was transferred", `ID_Cust` INT DEFAULT NULL COMMENT "core_custs.ID of customer for this order", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
- ID_Cust is for future use when customers can log in to retrieve their personal data
- ID_Session is for future use when sessions are tracked
shop_cart_lines
<mysql>DROP TABLE IF EXISTS `shop_cart_lines`; CREATE TABLE `shop_cart_lines` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Cart` INT NOT NULL COMMENT "shop_carts.ID", `ID_Item` INT NOT NULL COMMENT "cat_items.ID", `Qty` INT NOT NULL COMMENT "quantity ordered; 0 = removed from order", `WhenAdded` DATETIME NOT NULL COMMENT "when this item was first added to the order", `WhenEdited` DATETIME NOT NULL COMMENT "when the quantity for this item was last changed", `PriceItem` DECIMAL(9,2) NOT NULL COMMENT "price of item quoted at order time", `PriceShItm` DECIMAL(9,2) NOT NULL COMMENT "per-item shipping quoted for this item at order time", `PriceShPkg` DECIMAL(9,2) NOT NULL COMMENT "per-package shipping quoted for this item at order time",
/* These fields are redundant, and are used mainly to speed up cart display. They also preserve a record of
how the item was presented to the customer. */ `CatNum` VARCHAR(63) COMMENT "cat_items.CatNum of item as sold", `ID_Title` INT NOT NULL COMMENT "cat_titles.ID", `DescText` VARCHAR(255) NOT NULL COMMENT "plain-text description of item being ordered (e.g. for emails)", `DescHtml` VARCHAR(255) NOT NULL COMMENT "HTML description of item being ordered, for web-page display", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
shop_cust_data
This is a simple table for retaining customer data entered at check-out time before converting the cart to an order.
<mysql>DROP TABLE IF EXISTS `shop_cust_data`; CREATE TABLE `shop_cust_data` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Cart` INT NOT NULL COMMENT "shop_carts.ID", `ID_Key` INT NOT NULL COMMENT "shop_cust_data_types.ID of data type", `Val` VARCHAR(255) NOT NULL COMMENT "Value of data item", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
shop_cust_data_types
This table is probably unnecessary, but I can't convince myself of that. If nothing else, it will help prevent inconsistent usage.
<mysql>DROP TABLE IF EXISTS `shop_cust_data_types`; CREATE TABLE `shop_cust_data_types` (
`ID` INT NOT NULL AUTO_INCREMENT, `Name` VARCHAR(31) NOT NULL COMMENT "Short name, for reports or whatever", `Descr` VARCHAR(255) DEFAULT NULL COMMENT "Longer description of data item, e.g. usage notes", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
shop_log
Under construction
<mysql>DROP TABLE IF EXISTS `shop_log`; CREATE TABLE `shop_log` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Client` INT NOT NULL COMMENT "shop_clients.ID", `EvWhen` DATETIME NOT NULL COMMENT "When this event occurred", `Code` INT NOT NULL COMMENT "type of event", `Descr` VARCHAR(255) DEFAULT NULL COMMENT "Any details specific to this occurrence", `Notes` VARCHAR(255) DEFAULT NULL COMMENT "Human-entered notes", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
shop_clients
Under construction
<mysql>DROP TABLE IF EXISTS `shop_clients`; CREATE TABLE `shop_clients` (
`ID` INT NOT NULL AUTO_INCREMENT, `CRC` INT UNSIGNED NOT NULL COMMENT "crc32(Address+Browser) - unique integer defined by client specs", `Address` VARCHAR(15) NOT NULL COMMENT "IP address of client", `Domain` VARCHAR(255) COMMENT "Domain name of client", `Browser` VARCHAR(255) COMMENT "Browser USER_AGENT string", `WhenFirst` DATETIME NOT NULL COMMENT "When this client was first seen", `WhenFinal` DATETIME COMMENT "When this client was most recently seen", PRIMARY KEY(`ID`), UNIQUE KEY(`CRC`) ) ENGINE = MYISAM;</mysql>
- Decided to use CRC32 instead of compression, because the compressed strings always came out at least as long as the original string. They were probably designed to be effective for rather longer data.
shop_sessions
To be implemented. A new session should be created whenever the "same" client doesn't remember the session cookie (could be a different computer on the same IP address), or if the session has timed out.
Stock Management
stk_items
listing of what is now and what used to be in stock; depends on stk_bins and cat_items tables
<mysql>DROP TABLE IF EXISTS `stk_items`; CREATE TABLE `stk_items` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Bin` INT DEFAULT NULL COMMENT 'stk_bins.ID: which bin these items are in', `ID_Item` INT DEFAULT NULL COMMENT 'cat_items.ID: which exact type of item', `Qty` INT DEFAULT NULL, `WhenAdded` DATETIME DEFAULT NULL COMMENT 'when stock record was created for this item', `WhenChanged` DATETIME DEFAULT NULL COMMENT "when the quantity for this record was last altered (e.g. stock rmvd, or a split)", `WhenCounted` DATETIME DEFAULT NULL COMMENT "when this stock record was last verified by hand-count", `WhenRemoved` DATETIME DEFAULT NULL COMMENT "if not NULL, item is no longer in stock", `Cost` INT DEFAULT NULL COMMENT "what we paid (each) for these particular items in stock", `CatNum` varchar(63) DEFAULT NULL COMMENT "catalog number on tag (official cat# can change over time)", `Notes` TEXT, PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
stk_bins
containers in which stock may be found; depends on #stk_places
<mysql> DROP TABLE IF EXISTS `stk_bins`;
CREATE TABLE `stk_bins` ( `ID` int(11) NOT NULL auto_increment, `ID_Place` int(11) NOT NULL COMMENT 'stk_places.ID', `Code` varchar(15) NOT NULL COMMENT 'code name, e.g. NC01 -- must appear on outside of box', `Descr` varchar(63) default NULL COMMENT 'brief summary of contents', `WhenCreated` datetime default NULL COMMENT 'date when container was added to the database', `WhenVoided` datetime default NULL COMMENT 'date when container was destroyed or removed from usage', `WhenTainted` datetime default NULL COMMENT "if NOT NULL, this bin needs re-inventorying", `isForSale` tinyint(1) default NULL COMMENT "TRUE = this item is visible to customers as stock", `isForShip` tinyint(1) default NULL COMMENT "TRUE = this item is available for filling orders", `Notes` text, PRIMARY KEY (`ID`) ) ENGINE=MyISAM AUTO_INCREMENT=111 DEFAULT CHARSET=latin1;</mysql>
- WhenCreated can be NOT NULL if you don't have any legacy data to deal with.
- isForShip got eventually added because I kept forgetting to use WhenVoided, and I finally decided that it wasn't redundant to have a flag specifically for this attribute
stk_places
places where containers may be found, organized hierarchically
<mysql> DROP TABLE IF EXISTS `stk_places`;
CREATE TABLE `stk_places` ( `ID` INT NOT NULL AUTO_INCREMENT, `ID_Parent` INT COMMENT 'self.ID of place in which this place is found; may be NULL', `Name` VARCHAR(63) NOT NULL COMMENT 'brief descriptive name for listings and tree-paths', `Descr` VARCHAR(127) COMMENT '(optional) description of this place, so it can be located', PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
Both stock Bins and order Packages can be in Places. Later, Places may have attributes to tell us whether we can ship items directly from there (or at all) and whether or not they are effectively "local" to us, but for now they don't:
`isLocal` BOOL COMMENT `FALSE = items aren't here, so they must either be shipped here first or shipped to customer in separate pkg`, `isWhse` BOOL COMMENT `TRUE = items have to be fetched for shipping; some delay involved, and may want to print out lists of stuff to fetch`, `ID_Supp` INT COMMENT `NOT NULL = this location is a supplier's stock, and items must be ordered from it in order to be shippable`,
stk_history
log of all stock movement (depends on #stk_containers and #cat_items):
<mysql>DROP TABLE IF EXISTS `stk_history`;
CREATE TABLE `stk_history` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Stock` INT NOT NULL COMMENT "stk_items.ID of item being moved", `ID_Item` INT /* NOT NULL */ COMMENT "cat_items.ID of item being moved", `QtyFound` INT /* NOT NULL */ COMMENT "number of items found in this record before the move", `QtyDone` INT NOT NULL COMMENT "number of items moved (Qty fields at targ/dest may change value later)", `QtyLeft` INT NOT NULL COMMENT "number of items remaining after the move", `When` DATETIME NOT NULL COMMENT "when the move happened", `IDS_ContSrce` varchar(31) /* NOT NULL */ COMMENT "_stk_containers.IDS of where the item was moved from", `IDS_ContDest` varchar(31) /* NOT NULL */ COMMENT "_stk_containers.IDS of where the item was moved to", `IDS_LineSrce` varchar(31) /* NOT NULL */ COMMENT "hypothetical _stk_lines.IDS of where item came from", `IDS_LineDest` varchar(31) /* NOT NULL */ COMMENT "hypothetical _stk_lines.IDS of where item was moved to", `Notes` varchar(255) DEFAULT NULL COMMENT "optional explanatory notes", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
The IDS_Line* fields are somewhat redundant, but I decided that redundancy was a goal: one of the purposes of history data (including stk_history) is to help reconstruct what happened when something goes wrong. Maintenance of the _stk_containers table is also somewhat redundant, but should make displaying meaningful history reports quicker (i.e. it's basically a sort of cache, as are all the _* tables).
Commented-out bits can be included if you have no incomplete legacy data to deal with.
stk_bin_history
<mysql>DROP TABLE IF EXISTS `stk_bin_history`;
CREATE TABLE `stk_bin_history` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Bin` INT NOT NULL COMMENT "stk_bins.ID of bin being moved", `WhenDone` DATETIME NOT NULL COMMENT "when the move happened", `ID_Srce` INT COMMENT "stk_places.ID of where the bin came from (NULL = new bin)", `ID_Dest` INT COMMENT "stk_places.ID of where the bin was moved to (NULL = bin destroyed)", `Notes` varchar(255) DEFAULT NULL COMMENT "optional explanatory notes", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
Browsing
Topics and images
brs_topics
<mysql>DROP TABLE IF EXISTS `brs_topics`; CREATE TABLE `brs_topics` (
`ID` INT NOT NULL AUTO_INCREMENT, `ID_Parent` INT DEFAULT NULL COMMENT "brs_topics.ID of parent topic", `Name` varchar(128) NOT NULL COMMENT "generic name (context-free, but as short as possible)", `NameTree` varchar(64) DEFAULT NULL COMMENT "name within context of parent; defaults to Name", `NameFull` varchar(255) DEFAULT NULL COMMENT "descriptive standalone name (context free, can be long)", `Sort` varchar(15) DEFAULT NULL COMMENT "optional sorting key", `Variants` varchar(255) DEFAULT NULL COMMENT "synonyms and other keywords which should find this topic", `Mispeled` varchar(255) DEFAULT NULL COMMENT "same as Variants, but these are WRONG spellings; avoid displaying", PRIMARY KEY(`ID`)
) ENGINE = MYISAM;</mysql>
brs_titles_x_topics
<mysql>DROP TABLE IF EXISTS `brs_titles_x_topics`; CREATE TABLE `brs_titles_x_topics` (
`ID_Title` INT NOT NULL COMMENT "cat_titles.ID", `ID_Topic` INT NOT NULL COMMENT "brs_topics.ID", PRIMARY KEY(`ID_Title`,`ID_Topic`)
) ENGINE = MYISAM;</mysql>
Miscellaneous
- stats – computed variables, cache timestamps, and other variable stuff
<mysql> CREATE TABLE `stats` (
`Name` varchar(32) NOT NULL COMMENT 'name of variable', `Value` varchar(255) COMMENT 'value of variable', PRIMARY KEY(`Name`) ) ENGINE = MYISAM;</mysql>
- updates – timestamps on tables so we know when update queries need to be run
<mysql> CREATE TABLE `updates` (
`Name` varchar(32) NOT NULL COMMENT 'name of thing which gets updated', `Updated` datetime COMMENT 'when last updated', PRIMARY KEY(`Name`) ) ENGINE = MYISAM;</mysql>
Function to record an update:
<mysql> CREATE PROCEDURE SetUpdate(IN iName varchar(32))
BEGIN REPLACE INTO updates(Name,Updated) values(iName, NOW()); END</mysql>
Calculated Tables
_depts
<mysql> DROP TABLE IF EXISTS `_depts`;
CREATE TABLE `_depts` ( `ID` int(11) unsigned NOT NULL auto_increment, `ID_Supp` int(11) unsigned NOT NULL COMMENT 'needed to simplify calculation of _titles', `CatNum` varchar(63) NOT NULL, `CatKey` varchar(7), `CatWeb_Dept` varchar(63) NOT NULL, `CatWeb_Title` varchar(63) NOT NULL, `cntForSale` int DEFAULT NULL, `cntInPrint` int DEFAULT NULL, `qtyInStock` int DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;</mysql>
← depts x suppliers
SQL to fill _depts (except inPrint/inStock fields):
<mysql> CREATE PROCEDURE Upd_Depts_fr_Depts_Suppliers()
BEGIN DELETE FROM _depts; INSERT INTO _depts(`ID`,`ID_Supp`,`CatKey`,`CatNum`,`CatWeb_Dept`,`CatWeb_Title`) SELECT d.ID, d.ID_Supplier AS ID_Supp, UPPER(IFNULL(d.PageKey,d.CatKey)) AS CatKey, UPPER(CONCAT_WS("-",s.CatKey,d.CatKey)) AS CatNum, LOWER(CONCAT_WS("/",s.CatKey,IFNULL(d.PageKey,d.CatKey))) AS CatWeb_Dept, LOWER(CONCAT_WS("/",s.CatKey,d.CatKey)) AS CatWeb_Title FROM depts AS d LEFT JOIN suppliers AS s ON d.ID_Supplier=s.ID; END</mysql>
← _dept_ittyps by ID_Dept
<mysql> CREATE PROCEDURE Upd_Depts_fr_DeptIttyps()
UPDATE _depts AS d LEFT JOIN ( SELECT ID_Dept, SUM(di.cntForSale) AS cntForSale, SUM(di.cntInPrint) AS cntInPrint, SUM(di.qtyInStock) AS qtyInStock FROM _dept_ittyps AS di GROUP BY ID_Dept ) AS di ON di.ID_Dept=d.ID SET d.cntForSale = di.cntForSale, d.cntInPrint = di.cntInPrint, d.qtyInStock = di.qtyInStock;</mysql>
_titles
<mysql> DROP TABLE IF EXISTS `_titles`;
CREATE TABLE `_titles` ( `ID` int(11) unsigned NOT NULL, `ID_Supp` int(11) unsigned NOT NULL COMMENT "avoids need for joining with cat_depts", `CatNum` varchar(63) NOT NULL, `CatWeb` varchar(63) NOT NULL, `cntForSale` int DEFAULT NULL, `cntInPrint` int DEFAULT NULL, `qtyInStock` int DEFAULT NULL, `currMinSell` DECIMAL(9,2) DEFAULT NULL COMMENT "minimum selling price for an item of this title", `currMaxSell` DECIMAL(9,2) DEFAULT NULL COMMENT "maximum selling price for an item of this title", PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;</mysql>
← titles x _depts
<mysql> CREATE PROCEDURE Upd_Titles_fr_Depts()
REPLACE INTO _titles(ID,ID_Supp,CatNum,CatWeb) SELECT t.ID, d.ID_Supp, UPPER(CONCAT_WS("-",d.CatNum,t.CatKey)) AS CatNum, LOWER(CONCAT_WS("/",d.CatWeb_Title,t.CatKey)) AS CatWeb FROM titles AS t LEFT JOIN _depts AS d ON t.ID_dept=d.ID ORDER BY CatNum;</mysql>
← cat_items (grouped by ID_Title)
<mysql> CREATE PROCEDURE Upd_Titles_fr_CatItems()
UPDATE _titles AS t LEFT JOIN ( SELECT ID_Title, SUM(IF(i.isForSale,1,0)) AS cntForSale, SUM(IF(i.isInPrint,1,0)) AS cntInPrint, SUM(i.qtyInStock) AS qtyInStock, MIN(i.PriceSell) AS currMinSell, MAX(i.PriceSell) AS currMaxSell FROM cat_items AS i GROUP BY ID_Title ) AS ig ON ig.ID_Title=t.ID SET t.cntForSale = ig.cntForSale, t.cntInPrint = ig.cntInPrint, t.qtyInStock = ig.qtyInStock;</mysql>
← _title_ittyps (grouped by ID_Title)
<mysql> CREATE PROCEDURE Upd_Titles_fr_TitleIttyps()
UPDATE _titles AS t LEFT JOIN ( SELECT ID_Title, SUM(ti.cntForSale) AS cntForSale, SUM(ti.cntInPrint) AS cntInPrint, SUM(ti.qtyInStock) AS qtyInStock FROM _title_ittyps AS ti GROUP BY ID_Title ) AS ti ON ti.ID_Title=t.ID SET t.cntForSale = ti.cntForSale, t.cntInPrint = ti.cntInPrint, t.qtyInStock = ti.qtyInStock;</mysql>
_title_ittyps
list of available titles for each item type
<mysql> DROP TABLE IF EXISTS `_title_ittyps`;
CREATE TABLE `_title_ittyps` ( `ID_Title` int unsigned NOT NULL, `ID_ItTyp` int unsigned NOT NULL, `ID_Dept` int unsigned NOT NULL, `TitleName` varchar(127), `ItTypNameSng` varchar(63) COMMENT "cat_ittyps.NameSng", `ItTypNamePlr` varchar(63) COMMENT "cat_ittyps.NamePlr", `ItTypSort` varchar(31) COMMENT "cat_ittyps.Sort", `cntForSale` int COMMENT "# of different items available for type (either in stock or in print)", `cntInPrint` int COMMENT "# of different items (for type) in print for this title", `cntInStock` int COMMENT "# of different items (for type) which are for sale due to at least being in stock", `qtyInStock` int COMMENT "-1 = some in stock, but don't know how many", `currMinPrice` DECIMAL(9,2) DEFAULT NULL COMMENT 'minimum price for this item type', `currMaxPrice` DECIMAL(9,2) DEFAULT NULL COMMENT 'maximum price for this item type', `CatNum` varchar(63) DEFAULT NULL, `CatWeb` varchar(63) DEFAULT NULL, `CatDir` varchar(63) DEFAULT NULL, PRIMARY KEY (`ID_Title`,`ID_ItTyp`,`ID_Dept`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;</mysql>
- 2007-09-20 Added cntInStock field because we need to be able to figure out how many line items are available but not in stock (cntForSale-cntInStock)
← cat_items (grouped by ID_Title)
This procedure initially fills the table. (This is a revised version which combines two earlier procedures.) (I had a note that "the ID_Dept field is informational, not a grouping", but I don't remember what that meant and it doesn't make sense.)
<mysql> CREATE PROCEDURE Upd_TitleIttyps_fr_CatItems_Titles()
REPLACE INTO _title_ittyps( ID_Title, ID_ItTyp, ID_Dept, TitleName, cntForSale, cntInPrint, cntInStock, qtyInStock, currMinPrice, currMaxPrice) SELECT i.ID_Title, i.ID_ItTyp, t.ID_Dept, t.Name AS TitleName, i.cntForSale, i.cntInPrint, i.cntInStock, i.qtyInStock, i.currMinPrice, i.currMaxPrice FROM ( SELECT ID_Title, ID_ItTyp, SUM(IF(isForSale,1,0)) AS cntForSale, SUM(IF(isInPrint,1,0)) AS cntInPrint, SUM(IF(qtyInStock>0,1,0)) AS cntInStock, SUM(qtyInStock) AS qtyInStock, MIN(PriceSell) AS currMinPrice, MAX(PriceSell) AS currMaxPrice FROM cat_items AS i GROUP BY ID_Title, ID_ItTyp ) AS i LEFT JOIN titles AS t ON i.ID_Title=t.ID;</mysql>
- 2007-09-20
- Removed "WHERE i.isForSale" because this prevented the availability of discontinued items from being properly zeroed out
- Added cntInStock calculation; see table schema
← _titles
<mysql> CREATE PROCEDURE Upd_TitleIttyps_fr_Titles()
UPDATE _title_ittyps AS ti LEFT JOIN _titles AS t ON t.ID=ti.ID_Title SET ti.CatNum = t.CatNum, ti.CatWeb = t.CatWeb;</mysql>
← cat_ittyps
<mysql> CREATE PROCEDURE Upd_TitleIttyps_fr_CatIttyps()
UPDATE _title_ittyps AS ti LEFT JOIN cat_ittyps AS it ON ti.ID_ItTyp=it.ID SET ti.ItTypNameSng = it.NameSng, ti.ItTypNamePlr = it.NamePlr, ti.ItTypSort = it.Sort;</mysql>
_dept_ittyps
<mysql> DROP TABLE IF EXISTS `_dept_ittyps`;
CREATE TABLE `_dept_ittyps` ( `ID_ItTyp` int unsigned NOT NULL, `ID_Dept` int unsigned NOT NULL, `cntForSale` int unsigned NOT NULL, `cntInPrint` int unsigned NOT NULL, `qtyInStock` int unsigned NOT NULL, `ItTypNameSng` varchar(64) DEFAULT NULL COMMENT 'cat_ittyps.NameSng', `ItTypNamePlr` varchar(64) DEFAULT NULL COMMENT 'cat_ittyps.NamePlr; default to ItTypNameSng', PRIMARY KEY (`ID_ItTyp`,`ID_Dept`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;</mysql>
← _title_ittyps
<mysql> CREATE PROCEDURE Upd_DeptIttyps_fr_TitleIttyps()
REPLACE INTO _dept_ittyps(ID_ItTyp,ID_Dept,cntForSale,cntInPrint,qtyInStock) SELECT ID_ItTyp, ID_Dept, SUM(cntForSale) AS cntForSale, SUM(cntInPrint) AS cntInPrint, SUM(qtyInStock) AS qtyInStock FROM _title_ittyps GROUP BY ID_ItTyp, ID_Dept;</mysql>
← cat_ittyps
<mysql> CREATE PROCEDURE Upd_DeptIttyps_fr_CatIttyps()
UPDATE _dept_ittyps AS di LEFT JOIN cat_ittyps AS it ON it.ID=di.ID_ItTyp SET di.ItTypNameSng = it.NameSng, di.ItTypNamePlr = IFNULL(it.NamePlr,it.NameSng);</mysql>
_supplier_ittyps
<mysql> DROP TABLE IF EXISTS `_supplier_ittyps`;
CREATE TABLE `_supplier_ittyps` ( `ID` int(11) unsigned NOT NULL, `ItemType` varchar(63) NOT NULL, `ItemCount` int NOT NULL, `Name` varchar(63) NOT NULL, `CatKey` varchar(7) NOT NULL, PRIMARY KEY (`ID`,`ItemType`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;</mysql>
← suppliers x _titles x _title_ittyps x cat_ittyps ...
<mysql> CREATE PROCEDURE Upd_SupplierIttyps()
BEGIN DELETE FROM _supplier_ittyps; INSERT INTO _supplier_ittyps SELECT s.ID, if(Count(ti.ID_Title)=1,it.NameSng,it.NamePlr) AS ItemType, Count(ti.ID_Title) AS ItemCount, s.Name, s.CatKey FROM ( (suppliers AS s LEFT JOIN _titles AS tc ON tc.ID_Supp=s.ID) LEFT JOIN _title_ittyps AS ti ON ti.ID_title=tc.ID) LEFT JOIN cat_ittyps AS it ON ti.ID_ItTyp=it.ID GROUP BY s.ID, s.Name, s.CatKey, it.NameSng, it.NamePlr, ID_Parent HAVING SUM(tc.cntForSale) ORDER BY s.Name, SUM(tc.cntForSale) DESC; END</mysql>
Later we might refine this into a way to view unavailable suppliers as well... but that's very low priority, and it might be better to have a separate table for it anyway (keep "active" operations fast).
_stk_containers
Aggregate table listing all places where stock can be, for history:
<mysql> DROP TABLE IF EXISTS `_stk_containers`;
CREATE TABLE `_stk_containers` ( `IDS` varchar(31) NOT NULL COMMENT "Type + ID_forType: unique ID for this container, across all types", `Type` char(1) NOT NULL COMMENT "identifies which type of container each record refers to", `ID_forType` INT NOT NULL COMMENT "record's unique ID for that type", `Name` varchar(63) NOT NULL COMMENT "auto-generated but hopefully somewhat descriptive and human-readable name for this container", `When` DATETIME COMMENT "applicable date, as a somewhat more human-readable cue for some of the container types", PRIMARY KEY(`IDS`) ) ENGINE = MYISAM;</mysql>
← stk_bins, cat_items...
SQL to update (replace/fill) _stk_containers (in progress; Packages and Restocks not yet migrated):
<mysql>CREATE PROCEDURE Upd_StkContainers()
REPLACE INTO _stk_containers /* SELECT CONCAT("P.",p.ID) AS IDS, "P" AS Type, p.ID AS ID_forType, CONCAT(o.Number,"-",p.Seq) AS Name, s.WhenShipped AS TimeStamp FROM (Packages AS p LEFT JOIN Orders AS o ON p.ID_Order=o.ID) LEFT JOIN Shipments AS s ON p.ID_Shipment=s.ID; UNION /**/ SELECT CONCAT("L.",l.ID) AS IDS, "L" AS Type, l.ID AS ID_forType, l.Code AS Name, l.WhenCreated AS TimeStamp FROM stk_bins AS l UNION /* SELECT CONCAT("R.",r.ID) AS IDS, "R" AS Type, r.ID AS ID_forType, "po"&r.PurchOrdNum&"/ord"&SuppOrdNum&"/inv"&SuppInvcNum AS Name, RestockEffDate(r.ID) AS TimeStamp FROM Restocks AS r UNION /**/ SELECT CONCAT("M.",i.ID) AS IDS, "M" AS Type, i.ID AS ID_forType, i.CatNum AS Name, NULL AS TimeStamp FROM cat_items AS i WHERE i.IsMaster;</mysql>
Data Management
These tables are all maintained by hand, but must match what actually happens in the SQL code.
data_tables
<mysql>DROP TABLE IF EXISTS `data_tables`;
CREATE TABLE `data_tables` (
`ID` INT NOT NULL AUTO_INCREMENT, `Name` varchar(63) NOT NULL COMMENT "name of table being tracked", `WhenUpdated` DATETIME DEFAULT NULL COMMENT "when the table's data was last modified", `Notes` varchar(255) DEFAULT NULL COMMENT "descriptive notes", PRIMARY KEY(`ID`) ) ENGINE = MYISAM;</mysql>
data_procs
List of stored procedures which update one table from others
<mysql>DROP TABLE IF EXISTS `data_procs`;
CREATE TABLE `data_procs` (
`ID` INT NOT NULL AUTO_INCREMENT COMMENT "procedure ID", `Name` varchar(127) NOT NULL COMMENT "name of SQL stored procedure", `isActive` BOOL COMMENT "FALSE = skip this record", `doesClear` BOOL COMMENT "TRUE = clears all data from table before starting", `Notes` varchar(255) DEFAULT NULL COMMENT "explanatory notes", PRIMARY KEY (`ID`) ) ENGINE = MYISAM;</mysql>
data_flow
Keeps track of data dependencies between tables
<mysql>DROP TABLE IF EXISTS `data_flow`;
CREATE TABLE `data_flow` (
ID_Srce INT NOT NULL COMMENT "data_tables.ID of SOURCE table", ID_Dest INT NOT NULL COMMENT "data_tables.ID of DESTINATION table", ID_Proc INT NOT NULL COMMENT "data_procs.ID of stored procedure which calculates Dest data from Srce data", Notes varchar(255) COMMENT "loose explanatory or descriptive notes", PRIMARY KEY (`ID_Srce`,`ID_Dest`,`ID_Proc`) ) ENGINE = MYISAM;</mysql>
data_log
Log of all data updates performed internally
<mysql>DROP TABLE IF EXISTS `data_log`;
CREATE TABLE `data_log` (
ID INT NOT NULL AUTO_INCREMENT COMMENT "log line identifier", WhenStarted DATETIME NOT NULL COMMENT "when this event was started", WhenFinished DATETIME COMMENT "when it was completed", ID_TableDest INT NOT NULL COMMENT "data_tables.ID of table being updated", ID_TableSrce INT NOT NULL COMMENT "data_tables.ID of triggering/source table", ID_Proc INT NOT NULL COMMENT "data_procs.ID of stored procedure used", Caller varchar(63) COMMENT "identifying string from code which caused the update", Notes varchar(255) COMMENT "historical human-created notes", PRIMARY KEY (`ID`) ) ENGINE = MYISAM;</mysql>
Miscellaneous
error/message log
This is a log specifically of administration activity (shipping, catalog updates, etc.) so should probably be renamed admin_log or similar. There will be a separate log for shopping activity.
<mysql>DROP TABLE IF EXISTS `event_log`;
CREATE TABLE `event_log` (
ID INT NOT NULL AUTO_INCREMENT COMMENT "log line identifier", EvWhen DATETIME NOT NULL COMMENT "when the event started", EvWhere varchar(255) COMMENT "where in the code the event happened (suitable for filtering)", Params varchar(255) COMMENT "any relevant parameters", Descr varchar(255) COMMENT "description of error", Code INT DEFAULT NULL COMMENT "numeric error code unique to location (EvWhere)", VbzUser varchar(127) COMMENT "VbzCart username, for when we have a user system", SysUser varchar(127) COMMENT "who logged into the operating system", Machine varchar(64) COMMENT "network name of machine from which the event was initiated, if applicable", isError BOOL COMMENT "FALSE = this is just a message or normal event; TRUE = there is a problem to fix", isSevere BOOL COMMENT "TRUE = important enough to send email to admin immediately", Notes varchar(255) DEFAULT NULL COMMENT "manually-entered notes", PRIMARY KEY (`ID`) ) ENGINE = MYISAM;</mysql>
- Have to use "Ev" prefix because When and Where are keywords.
- Decided not to have "WhenStarted" and "WhenFinished" because:
- There are relatively few events that "complete"
- This leaves no place to record completion status (e.g. how many records were affected)
- It makes the code more complicated and less elegant
Future Changes
This is probably obsolete, and may have been partially implemented. Review later.
Shipping code should be tied to the item Option (i.e. no ID_ShipCode field in cat_items), which would include size and other variants (i.e. cat_itypes and cat_iopts will be combined). Each Supplier should have its own list of item types, to allow for different variation (e.g. LB tie-dye shirts of a given size weigh more than ZR shirts of the same size because they use thicker cloth). I'm using the schema as it is because converting the existing data will be a complicated task and we need to get this thing working ASAP, but I picture something like this in the near future:
- cat_itypes - catalog item types
- ID#@ - int(4)
- Name - text
- ID_Parent - int(4) -- cat_itypes.ID of parent type, if any
- cat_ioptns - catalog item options
- ID#@ - int(4)
- Key - string(8) -- Supplier.CatKey-Title.CatKey[-Key] = complete catalog number (Key can be blank)
- Descr - string(64) -- description of this option (e.g. "eXtra Large")
- cat_itype_ioptns - options available for each item type, per supplier
- ID - int(4) -- unique identifier
- ID_Supplier# - int(4)
- ID_IType# - int(4)
- ID_IOptn# - int(4)
- ID_ShipCode - int(4) -- shipping cost type for this combination
- Descr - string(128) -- description of this option/type combination (e.g. "eXtra Large shortsleeve t-shirt")
We would then remove the ID_IType and ID_ShipType fields from cat_items; ID_IOpt would point to cat_itype_ioptns instead of cat_ioptns... or we could rename it ID_ITypeOptn, but that's... a bit ugly. I'll have to talk myself into it. Also, I think I used a scheme something like this in an earlier revision of the MS Access version of VbzCart and ended up changing it to what it is now; that may have been for a good reason, or it may have been because it seemed too unwieldy... I think I was generating a cat_itype_ioptns entry for every possible combination of IType and IOptn, and that doesn't make sense. You just need a tool to help manage them, and to copy existing sets for tweaking. It also didn't have an ID_Supplier key.