fannie/classlib2.0/item/EpScaleLib.php
<?php
/*******************************************************************************
Copyright 2014 Whole Foods Co-op, Duluth, MN
This file is part of CORE-POS.
IT CORE is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
IT CORE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
in the file license.txt along with IT CORE; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*********************************************************************************/
namespace COREPOS\Fannie\API\item;
class EpScaleLib
{
static private $NEWLINE = "\r\n";
/**
Generate CSV line for a given item
@param $item_info [keyed array] of value. Keys correspond to WRITE_ITEM_FIELDS
@param $scale_model [ServiceScaleModel]
@return [string] CSV formatted line
*/
static public function getItemLine($item_info, $scale_model, $asNew)
{
$scale_fields = '';
if ($scale_model->epStoreNo() != 0) {
$scale_fields .= 'SNO' . $scale_model->epStoreNo() . chr(253);
}
$scale_fields .= 'DNO' . $scale_model->epDeptNo() . chr(253);
$scale_fields .= 'SAD' . $scale_model->epScaleAddress() . chr(253);
if (isset($item_info['Price' . $scale_model->storeID()])) {
$item_info['Price'] = $item_info['Price' . $scale_model->storeID()];
}
$labelInfo = array('labelType'=>103, 'descriptionWidth'=>26, 'textWidth'=>0);
if (isset($item_info['Label'])) {
$labelInfo = ServiceScaleLib::labelTranslate($item_info['Label'], $scale_model->scaleType());
}
// Always requested "add" might work better. Setting description
// isn't reliable when "updating" an item that doesn't actually
// exist on the other side of processing
/* ePlum nominally supports both an "add" and an "update"
* operation but the latter is not always reliable.
* Specifically, ePlum stores multiple descriptions where
* 'Description 1' is the first line of text, 'Description 2'
* is the second line, etc. Specifying two descriptions
* in an "add" command always works but specifying two
* descriptions in an "update" command sometimes ends
* up ignoring the second description.
*
* CORE doesn't only support ePlum; it also still supports
* Hobart's Data GateWeigh which also has separate
* "add and "update" operations. The goal of the current
* setup is to make it so you can feed the same '$items'
* data into both scale communication layers and generally
* have ePlum almost always use "add" operations but have
* Hobart switch appropriately between the two operations.
*/
if ($asNew || $item_info['RecordType'] == 'WriteOneItem') {
$line = self::getAddItemLine($item_info, $labelInfo) . $scale_fields;
} else {
$line = self::getUpdateItemLine($item_info, $labelInfo) . $scale_fields;
}
if ($scale_model->scaleType() == 'HOBART_HTI') {
preg_match('/UTA(\d\d\d)/', $line, $matches);
$tare = $matches[1];
$fixed_tare = substr($tare . '0', -3);
$line = str_replace($matches[0], 'UTA' . $fixed_tare, $line);
}
return $line;
}
static public function getIngredientLine($item_info, $scale_model)
{
$et_line = ($item_info['RecordType'] == 'WriteOneItem' ? 'CCOSIIA' : 'CCOSIIC') . chr(253);
if ($scale_model->epStoreNo() != 0) {
$et_line .= 'SNO' . $scale_model->epStoreNo() . chr(253);
}
$text_field = $item_info['ExpandedText'];
if (isset($item_info['ExpandedText' . $scale_model->storeID()])) {
$text_field = $item_info['ExpandedText' . $scale_model->storeID()];
}
$et_line .= 'DNO' . $scale_model->epDeptNo() . chr(253);
$et_line .= 'SAD' . $scale_model->epScaleAddress() . chr(253);
$et_line .= 'PNO' . $item_info['PLU'] . chr(253);
$et_line .= 'INO' . $item_info['PLU'] . chr(253);
$et_line .= 'ITE' . self::expandedText($text_field, $item_info) . chr(253);
return $et_line;
}
static private function expandedText($text, $item_info)
{
if ($item_info['MOSA']) {
$text = str_replace('{mosa}', 'Certified organic by MOSA', $text);
} else {
$text = str_replace('{mosa}', '', $text);
$text = str_ireplace('CERTIFIED ', '', $text);
}
if (!isset($item_info['OriginText'])) {
$item_info['OriginText'] = '';
}
$text = str_replace('{cool}', $item_info['OriginText'], $text);
if (isset($item_info['Reheat']) && $item_info['Reheat']) {
$text .= "\n\nReheat product to an internal temperature of 165 degrees F before consumption";
}
$text = str_replace("\r", '', $text);
return str_replace("\n", chr(0xE), $text);
}
static private function getAddItemLine($item_info, $labelInfo)
{
$line = 'CCOSPIA' . chr(253);
$line .= 'PNO' . $item_info['PLU'] . chr(253);
$line .= 'UPC' . ServiceScaleLib::pluToUPC($item_info['PLU']) . chr(253);
$desc = (isset($item_info['Description'])) ? $item_info['Description'] : '';
$line .= self::wrapDescription($desc, $labelInfo['descriptionWidth']);
$line .= 'DS1' . '0' . chr(253);
if (!strstr($line, 'DN2')) {
$line .= 'DN2' . chr(253);
$line .= 'DS2' . '0' . chr(253);
}
$line .= 'DN3' . chr(253);
$line .= 'DS3' . '0' . chr(253);
$line .= 'DN4' . chr(253);
$line .= 'DS4' . '0' . chr(253);
$line .= 'UPR' . (isset($item_info['Price']) ? round(100*$item_info['Price']) : '0') . chr(253);
$line .= 'EPR' . '0' . chr(253);
$line .= 'FWT' . (isset($item_info['NetWeight']) ? sprintf('%d0', $item_info['NetWeight']) : '0') . chr(253);
if ($item_info['Type'] == 'Random Weight') {
$line .= 'UMELB' . chr(253);
} elseif (isset($item_info['NetWeight']) && $item_info['NetWeight']) {
$line .= 'UMEFW' . chr(253);
} else {
$line .= 'UMEBC' . chr(253);
}
$line .= 'BCO' . '0' . chr(253);
$line .= 'WTA' . '0' . chr(253);
$line .= 'UTA' . (isset($item_info['Tare']) ? str_pad(floor(100*$item_info['Tare']).'0', 3, '0', STR_PAD_LEFT) : '0') . chr(253);
$line .= 'SLI' . (isset($item_info['ShelfLife']) ? $item_info['ShelfLife'] : '0') . chr(253);
$line .= 'SLT' . '0' . chr(253);
$line .= 'EBY' . '0' . chr(253);
$line .= 'CCL' . (isset($item_info['ReportingClass']) ? $item_info['ReportingClass'] : '0') . chr(253);
$line .= 'LNU' . '0' . chr(253);
$line .= 'GNO' . (isset($item_info['Graphics']) ? str_pad($item_info['Graphics'],6,'0',STR_PAD_LEFT) : '0') . chr(253);
$line .= 'GNU' . '0' . chr(253);
$line .= 'MNO' . '0' . chr(253);
$line .= 'INO' . $item_info['PLU'] . chr(253);
$line .= 'TNO' . '0' . chr(253);
$line .= 'NTN' . '0' . chr(253);
$line .= 'NRA' . '95' . chr(253);
$line .= 'ANO' . '0' . chr(253);
$line .= 'FTA' . 'N' . chr(253);
$line .= 'LF1' . (isset($item_info['Label']) ? $labelInfo['labelType'] : '0') . chr(253);
$line .= 'LF2' . '0' . chr(253);
$line .= 'FR1' . '0' . chr(253);
$line .= 'FDT' . '0' . chr(253);
$line .= 'PTA' . '0' . chr(253);
$line .= 'PC1' . chr(253);
$line .= 'EAS' . '0' . chr(253);
$line .= 'FSL' . 'N' . chr(253);
$line .= 'FUB' . 'N' . chr(253);
$line .= 'UF1' . chr(253);
$line .= 'UF2' . chr(253);
$line .= 'UF3' . chr(253);
$line .= 'UF4' . chr(253);
$line .= 'UF5' . chr(253);
$line .= 'UF6' . chr(253);
$line .= 'UF7' . chr(253);
$line .= 'UF8' . '1' . chr(253);
$line .= 'PTN' . '1' . chr(253);
return $line;
}
static private function getUpdateItemLine($item_info, $labelInfo)
{
$line = 'CCOSPIC' . chr(253);
foreach (ServiceScaleLib::$WRITE_ITEM_FIELDS as $key => $field_info) {
if (isset($item_info[$key])) {
switch ($key) {
case 'PLU':
$line .= 'PNO' . $item_info[$key] . chr(253);
$line .= 'UPC' . ServiceScaleLib::pluToUPC($item_info[$key]) . chr(253);
$line .= 'INO' . $item_info[$key] . chr(253);
break;
case 'Description':
if (strstr($item_info[$key], "\n")) {
list($line1, $line2) = explode("\n", $item_info[$key]);
$line .= 'DN1' . $line1 . chr(253);
$line .= 'DS1' . '0' . chr(253);
$line .= 'DN2' . $line2 . chr(253);
$line .= 'DS2' . '0' . chr(253);
} elseif (strlen($item_info[$key]) > $labelInfo['descriptionWidth'] && $labelInfo['descriptionWidth'] != 0) {
$line .= self::wrapDescription($item_info[$key], $labelInfo['descriptionWidth']);
} else {
$line .= 'DN1' . $item_info[$key] . chr(253);
$line .= 'DS1' . '0' . chr(253);
}
break;
case 'ReportingClass':
$line .= 'CCL' . $item_info[$key] . chr(253);
case 'Label':
/** disabled 11Nov2015 - doesn't syncing seems broken **/
$line .= 'LF1' . $labelInfo['labelType'] . chr(253);
break;
case 'Tare':
$line .= 'UTA' . str_pad(floor(100*$item_info['Tare']).'0', 3, '0', STR_PAD_LEFT). chr(253);
break;
case 'ShelfLife':
$line .= 'SLI' . $item_info[$key] . chr(253) . 'SLT0' . chr(253);
break;
case 'Price':
if ($item_info['Price'] != 0 && $item_info['Price'] < 9999) {
$line .= 'UPR' . round(100*$item_info[$key]) . chr(253);
}
break;
case 'Type':
if ($item_info[$key] == 'Random Weight') {
$line .= 'UMELB' . chr(253);
$line .= 'BCO' . '0' . chr(253);
} else {
$line .= 'UMEBC' . chr(253);
$line .= 'BCO' . '1' . chr(253);
}
break;
case 'NetWeight':
$line .= 'FWT' . sprintf('%d', $item_info[$key]) . chr(253);
break;
case 'Graphics':
$line .= 'GNO' . str_pad($item_info[$key],6,'0',STR_PAD_LEFT) . chr(253);
break;
case 'inUse':
$line .= 'UF8' . ($item_info[$key] ? 1 : 0) . chr(253);
break;
}
}
}
return $line;
}
static private function wrapDescription($desc, $length, $limit=2)
{
$lines = array($desc);
if ($length > 0) {
$wrapped = wordwrap($desc, $length, "\n", true);
$lines = explode("\n", $wrapped);
}
$keys = array_filter(array_keys($lines), function($i) use ($limit) { return $i<$limit; });
return array_reduce($keys, function($carry, $key) use ($lines) {
return $carry . 'DN' . ($key+1) . trim($lines[$key]) . chr(253)
. 'DS' . ($key+1) . '0' . chr(253);
});
}
/**
Write item update file(s) to ePlum
@param $items [keyed array] of values. Keys correspond to WRITE_ITEM_FIELDS
$items may also be an array of keyed arrays to write multiple items
One additional key, ExpandedText, is used to write Expanded Text. This
is separate from the Write Item operation so it's excluded from that
set of fields.
@param $scales [keyed array, optional] List of scales items will be written to
Must have keys "host", "type", and "dept".
May have boolean value with key "new".
*/
static public function writeItemsToScales($items, $scales=array(), $asNew=true)
{
$config = \FannieConfig::factory();
if (!isset($items[0])) {
$items = array($items);
}
$header_line = '';
$file_prefix = ServiceScaleLib::sessionKey();
$output_dir = $config->get('EPLUM_DIRECTORY');
if ($output_dir == '') {
return false;
}
$selected_scales = $scales;
if (!is_array($scales) || count($selected_scales) == 0) {
$selected_scales = $config->get('SCALES');
}
$scale_model = new \ServiceScalesModel(\FannieDB::get($config->get('OP_DB')));
$counter = 0;
$depts = array();
foreach ($selected_scales as $scale) {
$scale_model = ServiceScaleLib::getModelByHost($scale['host']);
// batches run per-department rather than per-scale
// so duplicates can be skipped
if ($scale_model === false) {
continue;
} elseif ($scale_model->epDeptNo() == 0 || in_array($scale_model->epDeptNo(), $depts)) {
continue;
} else {
$depts[] = $scale_model->epDeptNo();
}
/*
$file_name = sys_get_temp_dir() . '/' . $file_prefix . '_writeItem_' . $counter . '.dat';
$fptr = fopen($file_name, 'w');
fwrite($fptr, 'BNA' . $file_prefix . '_' . $counter . chr(253) . self::$NEWLINE);
foreach ($items as $item) {
$item_line = self::getItemLine($item, $scale_model, $asNew);
fwrite($fptr, $item_line . self::$NEWLINE);
if (isset($item['ExpandedText'])) {
$et_line = self::getIngredientLine($item, $scale_model);
fwrite($fptr, $et_line . self::$NEWLINE);
}
}
fclose($fptr);
// move to DGW; cleanup the file in the case of failure
if (!rename($file_name, $output_dir . '/' . basename($file_name))) {
unlink($file_name);
}
*/
$counter++;
}
}
/**
Delete item(s) from scale
@param $items [string] four digit PLU
or [array] of [string] 4 digit PLUs
*/
static public function deleteItemsFromScales($items, $scales=array())
{
$config = \FannieConfig::factory();
$dbc = \FannieDB::get($config->get('OP_DB'));
if (!is_array($items)) {
$items = array($items);
}
$file_prefix = ServiceScaleLib::sessionKey();
$output_dir = $config->get('EPLUM_DIRECTORY');
if ($output_dir == '') {
return false;
}
$selected_scales = $scales;
if (!is_array($scales) || count($selected_scales) == 0) {
$prep = $dbc->prepare("SELECT * FROM ServiceScales");
$selected_scales = $dbc->getAllRows($prep);
}
$scale_model = new \ServiceScalesModel(\FannieDB::get($config->get('OP_DB')));
$counter = 0;
$epScales = array();
foreach ($selected_scales as $scale) {
$file_name = sys_get_temp_dir() . '/' . $file_prefix . '_deleteItem_' . $counter . '.dat';
$fptr = fopen($file_name, 'w');
foreach ($items as $plu) {
if (isset($epScales[$scale['epDeptNo']])) {
continue;
}
if (strlen($plu) !== 4) {
// might be a UPC
$upc = str_pad($plu, 13, '0', STR_PAD_LEFT);
if (substr($upc, 0, 3) != '002') {
// not a valid UPC either
continue;
}
$plu = ServiceScaleLib::upcToPLU($upc);
}
fwrite($fptr, 'BNA' . $file_prefix . '_' . $counter . chr(253) . self::$NEWLINE);
$line = 'CCOSPID' . chr(253);
$line .= 'SNO' . $scale['epStoreNo'] . chr(253);
$line .= 'DNO' . $scale['epDeptNo'] . chr(253);
$line .= 'PNO' . $plu . chr(253);
fwrite($fptr, $line . self::$NEWLINE);
$line = 'CCOSIID' . chr(253);
$line .= 'SNO' . $scale['epStoreNo'] . chr(253);
$line .= 'DNO' . $scale['epDeptNo'] . chr(253);
$line .= 'INO' . $plu . chr(253);
fwrite($fptr, $line . self::$NEWLINE);
$epScales[$scale['epDeptNo']] = true;
}
fclose($fptr);
// move to DGW dir
if (!rename($file_name, $output_dir . '/' . basename($file_name))) {
unlink($file_name);
}
$counter++;
}
}
}