CORE-POS/IS4C

View on GitHub
documentation/IS4C/developer/howto-scanning.html

Summary

Maintainability
Test Coverage
<html>
<head><title>Scanning UPCs</title>
</head>
<body>
    <div style="text-align:center;margin-bottom:10px;font-size:80%;">
    updated as of: April 1, 2015<br />
    last author: Andy Theuninck
    </div>
    <div style="border: solid 1px black; font-size: 115%; padding: 1em;">
    The latest documentation can be found on the <a href="https://github.com/CORE-POS/IS4C/wiki/UPC-Parsing-Modularity">Project Wiki</a>.
    The information below may be out of date. 
    </div>
The UPC <a href="howto-parse.html">parser module</a> is currently
hard to trace through and should probably be more flexible. I think
it should [eventually] be modular in three ways: handle special
store-specific UPCs so they don't have to be hard-coded, allow 
discounts to be calculated differently by location, and allow
pricing methods to vary by location.
<hr />
<h3>Special UPCs</h3>
This collection of classes handles UPCs that aren't actually 
products. For example, at WFC I use UPC prefix "4" on barcoded
member cards and custom coupons; someone else may use the prefix
differently or just not need that code enabled. 
<p />
<i>Note: Special UPCs cannot exist in the products table or they
will be treated like normal items</i>
<p />
All Special UPC object should subclass SpecialUPC (lib/Scanning/SpecialUPC.php)
and implement two methods:
<ul>
<li>is_special(string upc) - return true is the module handles this upc, false
otherwise</li>
<li>handle(string upc, array json) - perform whatever operations the
upc should trigger. handle() should return a json array with the same
formatting as a parser object (<a href="howto-parse.html">Reference</a>).
The json argument passed in will have all the required fields; handle()
just needs to make updates.</li>
</ul>
<h3>Example</h3>
UPC 8005 switches to a member list at some store (or used to at some point and 
was still hardcoded in)
<pre>
class WackyPLU extends SpecialUPC {

    function is_special($upc){
        if ($upc == "0000000008005")
            return true;
        return false;
    }
    
    function handle($upc,$json){
        $json['main_frame'] = MiscLib::base_url().'gui-modules/memlist.php';
        return $json;
    }
}
</pre>
<hr />
<h3>Discount Types</h3>
Calculating discounts and pricing is done based on opdata.products.discounttype.
This column is an integer, and simply hard-coding checks for a specific value is
bound to eventually conflict with another location's usage. Values zero through
63 are reserved for use with DiscountType modules provided in the default
install. Values 64 through 127 may be customized on a per-coop basis to correspond
to any available DiscountType.
<p />
All Discount Type objects should subclass DiscountType (lib/Scanning/DiscountType.php)
and implement the following method:
<ul>
<li>priceInfo(array $row, integer $quantity) - given the $row from opdata.products,
this method should return a keyed array with four entries: regPrice, unitPrice,
discount, and memDiscount. These entries correspond with columns of the same
name in translog.localtemptrans.</li>
</ul>
There are four additional, optional methods that may be define as needed:
<ul>
<li>addDiscountLine() - if you want to add an informational "YOU SAVED" line
to the transaction, do so here.</li>
<li>isSale() - returns true if this is the discount type for "normal" pricing.
Parent version return false.</li>
<li>isMemberOnly() - returns true if the sale is only applicable to members.
Parent version return false.</li>
<li>isStaffOnly() - returns true is the sale is only applicable to staff.
Parent version return false.</li>
</ul>
<h3>Example</h3>
Here's a simple sale (see actual implementation for optional methods):
<pre>
class EveryoneSale extends DiscountType {

    function priceInfo($row, $quantity){
        $ret = array(
            'regPrice'  => $row['normal_price'],
            'unitPrice' => $row['special_price'],
            'memDiscount'  => 0
        );

        $ret['discount'] = ($ret['regPrice'] - $ret['unitPrice']) * $quantity;

        return $ret;
    }
}
</pre>
<hr />
<h3>Price Methods</h3>
Price Methods also deal with calculating prices and discounts, but
based on opdata.products.pricemethod (or opdata.products.specialpricemethod
for sales). There's a bit of overlap with Discount Types in that respect.
The key difference is that price method objects are responsible
for adding the item to the transaction. In general, additional price
methods beyond the basic case have to do with grouped sales and keep
track of which items in a transaction are part of the group. 
<p />
Like discount types, each store can specify an array of price method
modules in their configuration and opdata.products.pricemethod
maps into that array. The same reasons to avoid hard coding apply
here, too.
<p />
All Price Method objects should subclass PriceMethod (lib/Scanning/PriceMethod.php)
and implement the following method:
<ul>
<li>addItem(array $row, double $quantity, object $priceObj) - given
the row from opdata.products, quantity, and discount type object
(see above), this method should add the item to the transaction.
The return value of this method is currently ignored.
</li>
</ul>
<hr />
</body>
</html>