array( 'desc' => 'Turn on debugging (see DataObjects debugLevel )', 'default' => 0, 'short' => 'v', 'min' => 1, 'max' => 1, ), 'source' => array( 'desc' => 'Source directory for json files.', 'short' => 'f', 'default' => '', 'min' => 1, 'max' => 1, ), 'no-cache' => array( 'desc' => 'Do not cache.', 'short' => 'n', 'default' => 0, 'min' => 1, 'max' => 1, ), 'list-order' => array( 'desc' => 'List Order.', 'short' => 'l', 'default' => 0, 'min' => 1, 'max' => 1, ), 'table' => array( 'desc' => 'Only a specific table, eg. -t Netsuite_SalesOrder', 'short' => 't', 'max' => 1, ), 'year' => array( 'desc' => 'Only a specific year, eg. -y 2009', 'short' => 'y', 'default' => 2009, 'min' => 1, 'max' => 1, ), 'mapped' => array( 'desc' => "Get a mapped value or 'ALL' to show everything", 'short' => 'm', 'default' => 0, 'min' => 1, 'max' => 1, ), 'where' => array( 'desc' => "Import only items matching this condition, eg. --where='ItemFulfillment_id=123'", 'short' => 'w', 'default' => '', 'min' => 1, 'max' => 1, ), 'print' => array( 'desc' => "print_r the data for import (matching -w)", 'short' => 'p', 'default' => 0, 'min' => 0, 'max' => 1, ), ); function getAuth() { $ff = HTML_FlexyFramework::get(); if (!$ff->cli) { die("run form cli only"); } } var $map = array(); var $tables = array(); var $opts = array(); function initOpts($opts) { $this->opts = $opts; //print_R($opts); if (empty($this->opts['source'])) { $this->opts['source'] = __DIR__.'/data'; } $sd = strtoupper(substr( HTML_FlexyFramework::get()->database, -2)); $this->department = $sd; $os = $this->opts['source'] ; $this->opts['source'] .= '/'. $sd; //print_R($this->opts);exit; if (!is_dir($this->opts['source'])) { if (file_exists($os)) { $this->opts['source'] = $os; } else { die("--source must be a directory {$this->opts['source']}\n"); } } } var $department = 'UNKNOWN'; function cachedir() { $uinfo = posix_getpwuid( posix_getuid () ); $user = $uinfo['name']; $cd = ini_get('session.save_path') ."/xtuple{$this->department}-{$user}"; if (!file_exists($cd)) { mkdir($cd, 0700, true); } return $cd; } function get($path, $opts) { ini_set('memory_limit', -1); // unlimited! PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($this, 'onPearError')); $this->initOpts($opts); $cd = $this->cachedir(); if (!empty($opts['mapped']) && !empty($opts['table'])) { require_once 'Pman/Xtuple/Migration/Base.php'; $b = new Pman_Xtuple_Migration_Base($this); echo "Mapped value: "; $this->map(preg_replace('/^Netsuite_/i', '',$opts['table'])); print_r( $opts['mapped'] == 'ALL' ? $this->map[preg_replace('/^Netsuite_/i', '',$opts['table'])] : $b->mapped( preg_replace('/^Netsuite_/i', '',$opts['table']), $opts['mapped'] ) ); echo "\n"; exit; } $this->buildDeps(); $nocache = $this->opts['no-cache']; // print_R($this->deps); exit; // push salesorderitem up the list.. $this->deps['Netsuite_Invoice'][] = 'Netsuite_SalesOrderItem'; //$this->deps['Netsuite_Invoice'][] = 'Netsuite_SalesOrder'; $this->deps['Netsuite_Rates'] = array('Netsuite_Currency'); $this->deps['Netsuite_VendorBill'] = array('Netsuite_Rates'); $this->deps['Netsuite_PurchaseOrder'] = array('Netsuite_Rates'); $this->deps['Netsuite_ItemReceipt'] = array('Netsuite_Rates'); $this->deps['Netsuite_JournalEntry'] = array('Netsuite_Rates'); $this->deps['Netsuite_VendorPayment'][] = 'Netsuite_VendorBill'; $this->deps['Netsuite_CustomerPayment'][] = 'Netsuite_CustomerRefund'; $this->deps['Netsuite_CustomerRefund'][] = 'Netsuite_CreditMemo'; // delay inventory transfer? $this->deps['Netsuite_InventoryTransfer'][] = 'Netsuite_InventoryItemLocations'; // $this->deps['Netsuite_InventoryTransfer'][] = 'Netsuite_VendorBill'; // bump up customer payment //$this->deps['Netsuite_VendorBill'][] = 'Netsuite_CustomerPayment'; $this->deps['Netsuite_Check'][] = 'Netsuite_VendorBill'; $this->deps['Netsuite_VendorReturnAuthorization'][] = 'Netsuite_VendorBill'; $this->deps['Netsuite_Check'][] = 'Netsuite_VendorBill'; $this->deps['Netsuite_VendorCredit'][] = 'Netsuite_VendorBill'; $this->deps['Netsuite_Transfer'] = array( 'Netsuite_JournalEntry'); $this->tables[] = 'Netsuite_Transfer'; $this->deps['Netsuite_Deposit'] = array( 'Netsuite_JournalEntry'); $this->tables[] = 'Netsuite_Deposit'; $this->deps['Netsuite_InventoryTransfer'][] = 'Netsuite_ItemReceipt'; $this->deps['Netsuite_InventoryAdjustment'][] = 'Netsuite_ItemReceipt'; // locations have to have customers. $this->deps['Netsuite_Location'][] = 'Netsuite_Customer'; $this->deps['Netsuite_InventoryItem'][] = 'Netsuite_Location'; $this->deps['Netsuite_ItemReceipt'][] = 'Netsuite_InventoryItem'; $this->deps['Netsuite_PurchaseOrder'][] = 'Netsuite_InventoryItem'; $this->deps['Netsuite_VendorBill'][] = 'Netsuite_InventoryItem'; //$this->deps['Netsuite_VendorBill'][] = 'Netsuite_Location'; // remove me later...- when elliot has finished it. //$this->deps['Netsuite_ExpenseReport'][] = 'Netsuite_CustomerPayment'; //$this->deps['Netsuite_CreditMemo'] = array('Netsuite_InventoryItem'); array_unshift($this->tables, 'Netsuite_Rates'); $this->tables = $this->sortDeps($this->tables); /// append Netsuite_ if doesn't already exist if (empty($this->opts['table'])) { // nothing } else if(is_string($this->opts['table'])) { $this->opts['table'] = (strrpos($this->opts['table'], 'Netsuite_') !== false) ? $this->opts['table'] : 'Netsuite_'.$this->opts['table']; } else if (is_array($this->opts['table'])) { foreach($this->opts['table'] as &$t) { $t = (strrpos($t, 'Netsuite_') !== false) ? $t : 'Netsuite_'.$t; } } if (!empty($this->opts['table']) && !in_array( $this->opts['table'], $this->tables)) { die("Invalid table ! - {$this->opts['table']}\n"); } //print_R($this->tables); if ($this->opts['list-order']) { echo implode("\n", $this->tables) ."\n";exit; } /// make sure all our accounting periods are open... $per = DB_DataObject::Factory('period'); $per->query('UPDATE period set period_closed=false'); // remove invalid taxzones.. $per->query("delete from taxass where taxass_taxzone_id = ( SELECT taxzone_id FROM taxzone where taxzone_descrip='State Sales Tax Authority' )"); $per->query("delete from taxzone where taxzone_descrip='State Sales Tax Authority'"); $wkey = false; if (!empty($this->opts['where'])) { if (empty($this->opts['table'])) { die("you must specify table if using --where\n"); } list($wkey, $wval) = explode('=', $this->opts['where']); } $y = $this->opts['year']; $done = array(); foreach($this->tables as $tbl) { $n = preg_replace('/^Netsuite_/i', '', $tbl); // skip these tables if ($n == 'TransactionApply') { // we actually use it for all the transaction based tables, // and our dump code now merges it into the parent transactions. continue; } // should we use map() for this? $f = $this->opts['source'] ."/{$tbl}.sql.json"; $all = file_exists($f.'.all') ; $f = $all ? $f.'.all' : $f.'.'.$y; $yy = $all ? 'all' : $y; echo "Migrating $tbl from $f\n"; if (empty($this->opts['table'])) { $export_run = false; foreach (array( 2008,2009,2010,2011,2012) as $yr) { $f = $this->opts['source'] ."/{$tbl}.sql.json"; $cyr = $all ? 'all' : $yr; $f = $all ? $f.'.all' : $f.'.'.$yr; $cache = $cd ."/" .md5($f) . "-{$tbl}-{$cyr}.sql.cache"; $sourcetime = file_exists($f) ? filemtime($f) : 0; $is_other = false; if ($n == 'Transfer' || $n == 'Deposit') { // no checks done.. } else { if (!$sourcetime && !$is_other) { echo "SKIP FILE - does not exists : $f\n"; continue; } $migtime = file_exists(dirname(__FILE__) ."/Migration/{$n}.php") ? filemtime(dirname(__FILE__) ."/Migration/{$n}.php") : 0; $cachetime = file_exists($cache) && filesize($cache) ? filemtime($cache) : 0; print_R(array( $f=> $sourcetime, 'PHP'=>$migtime,$cache => $cachetime)); if ($cachetime >= $sourcetime && $cachetime >= $migtime) { echo "SKIP FILE UP TO DATE? $tbl : $yr \n"; continue; } if (!$migtime) { echo "SKIP TABLE - No convertor exists $tbl : $yr \n"; continue; } if (!$export_run) { $this->runExport($tbl); $export_run = true; } } echo "RUNNING AS CACHE MISSING / OUT OF DATE: $cache \n"; $cmd = implode(' ', array( '/usr/bin/php', // php $_SERVER['SCRIPT_NAME'], // hk.php 'Xtuple/Migrate', '-t ' . $tbl, '-y ' . $yr, '-f ' . $this->opts['source'] )); echo "\n\n-------------------------\n$cmd\n-----------------\n\n"; $ret = 0; passthru( $cmd , $ret ); $ret = (int) $ret; if ($ret !== 4 ) { var_dump($ret); die("FAILED "); } if ($all) { break; } } continue; } $cache = $cd ."/" .md5($f) . "-{$tbl}-{$yy}.sql.cache"; if ($n == 'Transfer' || $n == 'Deposit') { if (!empty($this->opts['table']) && $this->opts['table'] != $tbl) { continue; } if (file_exists($cache)) { // continue; } //DB_DataObject::DebugLevel(1); $cl = $this->factory($n); $cl->postMigrate(); touch($cache); echo "Completed ". $this->opts['table'] . "\n"; exit(4); continue; } if (!file_exists($f)) { $this->updateReport($n, "Skipped - no dump exists "); echo "Skip $n - no dump exists $f \n"; echo "creating empty cache $cache\n"; $done[] = $tbl; file_put_contents($cache, serialize(array())); if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) { echo "Completed ". $this->opts['table'] . "\n"; exit(4); } continue; } //var_dump($this->opts); if ($this->opts['debug']) { DB_DataObject::debugLevel( $this->opts['debug'] ); } $cl = $this->factory($n); if (!$cl) { $this->updateReport($n, "Skipped - no migration tool exists"); if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) { echo "Skipped - no migration tool exists ". $this->opts['table'] . "\n"; exit(4); } continue; } $doimport = empty($this->opts['table']) || $this->opts['table'] == $tbl; $usecache = !$nocache; if (!empty($this->opts['table'])) { if ($this->opts['table'] == $tbl) { $usecache = false; } } $sourcetime = filemtime($f); $migtime = filemtime(dirname(__FILE__) ."/Migration/{$n}.php"); $cachetime = file_exists($cache) && filesize($cache) ? filemtime($cache) : 0; if ($usecache && $cachetime >= $sourcetime && $cachetime >= $migtime) { // use cache.. $this->map($n); //$doimport ? $cl->validateUsed((array)json_decode($data[1])) : false; $done[] = $tbl; $this->updateReport($n, false); echo "Migrating $tbl - using cache : $cache \n"; continue; } if ($nocache && $cachetime < $sourcetime) { echo "Migrating $tbl - cache to old, or does not exist\n"; } if ($nocache && $cachetime < $migtime) { echo "Migrating $tbl - migration code was updated\n"; } // finally do the migration.. $cl->validateOutputSupport(); $data = file($f); $missing_dep = false; foreach( $this->deps[$tbl] as $dep) { if (in_array($dep, $done)) { continue; } echo "Checking for dep $dep\n"; if ($this->map(preg_replace('/^Netsuite_/', '', $dep))) { continue; } $missing_dep = $dep; break; } if ($missing_dep !== false) { echo "Skip $n - dependant table $missing_dep has not been done yet.\n"; $this->updateReport($n, "Skip dependant table $missing_dep has not been done yet."); if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) { echo "Failed ". $this->opts['table'] . "\n"; exit(3); // error condtion.. } exit(3); continue; } if (!$doimport ) { echo "Skip $n - no specified by --table .\n"; $this->updateReport($n, false); if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) { echo "Completed ". $this->opts['table'] . "\n"; exit(4); } continue; } $cl->validateUsed((array)json_decode($data[1])); $def = false; $this->map[$n] = array(); $total = count($data); echo "Migrating $total Records\n"; $last = 0; foreach($data as $i=> $l) { if (empty($cl->def)) { // first line is default.s $cl->def = $l; continue; } // print out some progress. if ($i % 500 == 0) { echo floor(($i/$total) * 100)."%\n"; } $row = (array)json_decode($l); // testing a single entry.. if ($wkey) { if (!isset($row[$wkey])) { die("--where condition is invalid, key '$wkey' does not exist\n"); } if ($row[$wkey] != $wval) { continue; } if (!empty($opts['print'])) { print_R($row); } //DB_DataObject::debugLevel(1); } $add = $cl->migrate($row); if ($wkey) { echo "Migrate return\n"; var_dump($add); } // rates does not have an id col. if ($add !== false ) { //echo "SAVEMAP {$row['id']} : $add \n"; $cl->saveMap(isset($row['id']) ? $row['id'] : $i, $add); } // free up some memory.. $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array(); $GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array(); } //var_Dump($n); // works ok here.. //print_R(array_keys($this->map)); echo "Migrating $tbl - ". count(array_keys($this->map[$n])) . " Records added\n"; $cl->postMigrate(); if ($wkey) { die("ONLY RUNNING IN TESTING MODE with --where condition exiting before any damage is done..\n"); } $str = $cl->resultsToString(); $this->updateReport($n, $cl->resultsToString()); if ($str !== false) { file_put_contents($cd ."/{$tbl}-{$yy}.results.txt", $str); echo "RESULTS IN {$cd}/{$tbl}-{$yy}.results.txt\n"; } // sorting is pretty irrelivant, and screws things up horriably anyway. //ksort($this->map[$n],SORT_STRING); //var_dump($this->map[$n][3263]); file_put_contents($cache, serialize($this->map[$n])); echo "CACHED DATA IN php -r 'print_r(unserialize(file_get_contents(\"{$cache}\")));'\n"; touch($cache, max($sourcetime, $migtime, filemtime($cache))); $done[] = $tbl; if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) { //print_r($this->map[$n]); //var_dump($this->map[$n][3263]); echo "Completed ". $this->opts['table'] . "\n"; exit(4); } die("\nRun again - to import more\n"); } die("DONE"); //die("DONE\n"); } function updateReport($n, $res) { $cd = $this->cachedir(); $f = $this->opts['source'] ."/Netsuite_{$n}.sql.json"; // we need to update individual reports, and a 'merged one'.. if ($res !== false) { file_put_contents( "{$cd}/" .md5($f) . "-Netsuite_{$n}.results.txt", $res); } else { $res = file_exists("{$cd}/" .md5($f) . "-Netsuite_{$n}.results.txt") ? file_get_contents("{$cd}/" .md5($f) . "-Netsuite_{$n}.results.txt") : "No results available"; } static $latest = false; if (!$latest) { $latest = "{$cd}/latest.results.txt"; file_put_contents($latest, "Import Report - " . date("Y-m-d H:i:s"). "\n\n"); } $fh = fopen($latest, 'a'); fwrite($fh, "RESULTS FOR: $n:\n"); fwrite($fh, "$res\n\n"); fclose($fh); } function buildDeps() { $deps = array(); $ini = parse_ini_file(dirname(__FILE__).'/Migration/netsuite.links.ini', true); foreach($ini as $tbl => $maps) { if (!in_array($tbl, $this->tables)) { $this->tables[] = $tbl; $deps[$tbl] = array(); } foreach($maps as $col=>$to) { $kv = explode(':', $to); if (!in_array($kv[0], $this->tables)) { $this->tables[] = $kv[0]; $deps[$kv[0]] = array(); } if (in_array($kv[0], $deps[$tbl])) { continue; } $deps[$tbl][] = $kv[0]; } } $this->deps = $deps; } /** * * has the map been loaded.. */ var $loaded = array(); function map($n) { if (isset($this->loaded[$n])) { return true; } $this->loaded[$n] = true; echo "RESET MAP FOR $n\n"; $this->map[$n] = array(); $cd = $this->cachedir(); $years = array('all', 2008,2009,2010,2011,2012); $tbl = 'Netsuite_'. $n; // make sure teh map is loaded for a class.. $ret = false; foreach($years as $yy) { $f = $this->opts['source'] ."/{$tbl}.sql.json.". $yy; $cache = $cd ."/" .md5($f) . "-{$tbl}-{$yy}.sql.cache"; if (!file_exists($cache) || !filesize($cache)) { echo "NO CACHE $cache\n"; continue; } $ret = true; echo "LOADED CACHE $cache\n"; $this->map[$n] += unserialize(file_get_contents($cache)); if ($yy == 'all') { break; } } if (!$ret) { unset($this->map[$n]); } return $ret; } function sortDeps($ar) { // this could be done by usort, but we need to build a reverse dependancy list as well. // loop through an array, // if we can add an item - add it, otherwise push it to the end.. $ret = array(); $done = array(); for($i = 0 ; $i < count($ar); $i++ ) { $ltest = array(); while (true) { $t = $ar[$i]; if (isset($ltest[$t])) { echo "DEPENDANCIE FAILED FOR $t"; exit; } // no dependancies.. it can be added.. if (empty($this->deps[$t])) { $done[$t] = true; break; } // have all the dependancies been added.. $missing = false; foreach($this->deps[$t] as $dep) { if (!isset($done[$dep])) { $missing = true; break; } } if (!$missing) { $done[$t] = true; break; } // a dependancy is missing.. // move this element to the end.. unset($ar[$i]); $ar = array_values($ar); // renumber... $ar[] = $t; $ltest[$t] = true; // try ataing.. } } return $ar; } function factory($n) { $parts = explode('/', $n); $n = $parts[0]; $cf = dirname(__FILE__) ."/Migration/{$n}.php"; $cn = "Pman_Xtuple_Migration_{$n}"; if (!file_exists($cf)) { echo "Skip $n - no file '$cf' exists yet\n"; return false; } require_once $cf; if (!class_exists($cn)) { echo "SKIP class does not exist in file: $cn\n"; return false; } $cl = new $cn($this, isset($parts[1]) ? $parts[1] : false); return $cl; } function onPearError($err) { $out = $err->toString(); //print_R($bt); exit; $ret = array(); foreach($err->backtrace as $b) { $ret[] = $b['file'] . '(' . $b['line'] . ')@' . @$bt['class'] . '::' . @$bt['function']; } //convert the huge backtrace into something that is readable.. $out .= "\n" . implode("\n", $ret); echo $out; echo "\n-- FAILED --\n"; exit(3); } function runExport($tbl) { echo "RUNNING EXPORT to ensure data is up-to-date: $tbl \n"; $country = strtoupper($this->department); // this is in Pman.Xtuple.Migrate directory now.. $cmd = implode(' ', array( '/usr/bin/php', // php __DIR__ . '/../web.Netsuite/index.php', 'Netsuite/Dump', '-t ' . $tbl, '-c ' . $country )); echo "\n\n-------------------------\n$cmd\n-----------------\n\n"; //exit; //$ret = 0; passthru( $cmd , $ret ); } }