CORE-POS / IS4C

Cooperative Operational Retail Environment
http://www.core-pos.com
GNU General Public License v2.0
63 stars 44 forks source link

Support for GS1 DataBar produce stickers #1214

Open flathat opened 6 months ago

flathat commented 6 months ago

This is about adding support for GS1 DataBar produce stickers, the ones that look like the image below. They have a GS1 barcode and usually the PLU of the item. (The number below the image of the sticker, 0100896247001111, is what a hand scanner returns.) GS1-DataBar-Omni

The scanner/scale has to be able to read this type of barcode. The Magellan 9300i/9400i Product Reference Guide p 333 PDF 345 says DataBar Omnidirectional, the type of DataBar produce stickers use, is "factory programmed" meaning the scanner/scale may need to be sent to the factory to even support being enabled. I wasn't aware of this when I ordered the unit I tested on and hadn't asked for it but it turned out to be available; I suggest you follow the enablement instructions before you assume otherwise. Even if it is available it will be disabled by default. Datalogic technical support is helpful and may be able to tell you if your unit has the feature.

Details on support and enablement of DataBar Omnidirectional on other Magellan scanner/scale models are at the end of this comment.

When I started working on this I thought the sticker DataBars would return a PLU but they actually return full UPCs (actually GTIN-14, 00896247001111 in the example above); some vendors use the PLU in their UPC but others don't. The PLU is not in a separate field after the GTIN in any of the dozen examples I've looked at. This implies that you'll need a separate Item record for each brand of stickered fruit.

If you're having trouble with the capability or enablement of your scanner/scale you can scan the stickers with an enabled hand scanner. The code it reads won't be directly usable in CORE-POS but if you type GS1~R4 and then hand-scan the sticker you'll get something like GS1~R40100896247001111 and can see what will happen (if the changes below are implemented).

GS1 DataBar (the "regular" UPC and EAN barcodes are also part of the GS1 system but are not DataBar) is detected and labelled by the scanner/scale. The scanner/scale driver, usually SPH_Magellan_Classic, labels it further such that it can be detected by the parsing class in UPC.php, which distinguishes the different types of DataBars and either handles them if they identify a known item or passes them on to other methods (such as the one for DataBar Coupons) or to an unknown-item handler; latter is what currently happens with produce sticker codes.

UPC.php currently does not check for the the type of DataBar on produce stickers, for which I think Omnidirectional is the correct name, to distinguish it from the Expanded type which is used for coupons and which it does check for.

UPC.php includes code to parse Omnidirectional DataBars which , if Stacked, like the produce sticker ones usually are, can include several fields, each prefixed by an Application Identifier (AI). The AI for a GTIN-14 is 01. UPC.php includes a test for 10, not 01, under a comment that says it is testing for GTIN-14. 10 is also a legal AI but it refers to Batch/Lot which is not used in CORE-POS.

The CORE-POS Wiki page GS1 DataBar Support includes a description of the code for the GS1 DataBar "regular" (Omnidirectional) and the GTIN Application Indicator. If UPC.php is meant to be the implementation of that then I suggest the following changes to the UPC class and its methods:

# Add:
const GS14_PREFIX = 'GS1~R4';
const GS14_STATUS = 'NA';
# Better names might be  GS1_ITEM_PREFIX and GS1_ITEM_STATUS
# and then use GS1_EXPANDED_PREFIX instead of GS1_PREFIX for 'GS1~RX'

# Change prefixes() to reflect the const's

# In function parse() add:
if ($this->source == self::GS14_PREFIX) {
    $str = $this->fixGS1($str);
}

# In function fixGS1() :
# Instead of ...
// GTIN-14; return w/o check digit,
        // ignore any other fields for now
        if (substr($str,0,2) == "10")
            return substr($str,2,13);
# ... this,  which looks for the correct Application Indicator
and is aware of the site-sepcific flag UpcIncludeCheckDigits
/* GTIN-14; return w/ or w/o check digit,
         * ignore any other fields for now
         */
        if (substr($str,0,2) == "01") {
            //return substr($str,2,13);
            return substr($str,
                ($this->session->get("UpcIncludeCheckDigits")) ? 3 : 2, 
                13); // 9Feb'24 EL corrected to include this
        }

# In function sanitize() 
# After:
// leave GS1 barcodes alone otherwise
if ($this->source == self::GS1_PREFIX) {
    return $entered;
}
# add:
// leave GS1 item barcodes alone otherwise
if ($this->source == self::GS14_PREFIX) {
    return $entered;
}

If this is acceptable I can put it in a pull request.

The reference for GS1, including DataBar, is GS1 General Specifications Standard v24

GS1 DataBar Support and Enablement in other Magellan scanner/scales:

gohanman commented 6 months ago

This looks good to me. The only question that occurs to me is: does the scale have an option to drop check digits on these? A GTIN-14 that doesn't have a leading zero isn't going to fit in the 13-digit UPC fields, and widening that field throughout the project would be a big undertaking.

flathat commented 6 months ago

AT> does the scale have an option to drop check digits on these?
EL>The 9400i doesn't seem to.

The length is missing in what I wrote, s/b:

            return substr($str,
                ($this->session->get("UpcIncludeCheckDigits")) ? 3 : 2,
                13);

I didn't find a GTIN with a non-0 first character until I'd done the above. If one is including checkdigits there isn't anything that can be done; it wouldn't have been possible to assign a products.upc with the whole GTIN anyway, so the scan of the sticker can't succeed.

flathat commented 6 months ago

I've tested this trap for that situation.

        if (substr($str,0,2) == "01") {
            if ($this->session->get("UpcIncludeCheckDigits") &&
                    $str[2] != '0') {
                return sprintf("%s|%s|%s", '*PROBLEM',
                    _("Cannot handle this item ID.  Perhaps try the PLU on the sticker."),
                    substr($str,2));
            }
            return substr($str,
                ($this->session->get("UpcIncludeCheckDigits")) ? 3 : 2,
                13);
        }

and then parse() does this:

/* If fixGS1() found a problem report it and return.
 */
if (strpos($str,'*PROBLEM') === 0)
{
    /** $problem
     * [0] '*PROBLEM'
     * [1] A description of the problem
     * [2] The string being parsed at the point the problem was found.
     */
    $problem = explode('|',$str);
    $ret = $this->default_json();
    $problem[1] = str_replace('  ','<br />',$problem[1]);
    $msg = sprintf("%s<br />%s%s", $problem[1], _('Item: '), $problem[2]);
    $ret['output'] = DisplayLib::boxMsg(
        $msg,
        _('DataBar Problem'),
        false,
        DisplayLib::standardClearButton()
    );
    return $ret;
}

image