4 * migration script runner..
5 * xx.php Xtuple/Migrate
7 * This is the controller that runs all the table migrations.
9 * it' calls it'self.. to try and reduce memrory
11 * Caching is a bit of a nightmare..
13 * a) run the whole thing Xtuple/Migrate
14 * b) run a specific year/table combo Xtuple/Migrate -t Netsuite_XXX -y 2010
21 require_once 'Pman.php';
23 class Pman_Xtuple_Migrate extends Pman
25 static $cli_desc = "Migrate Netsuite json files to xtuple";
27 static $cli_opts = array(
29 'desc' => 'Turn on debugging (see DataObjects debugLevel )',
38 'desc' => 'Source directory for json files.',
45 'desc' => 'Do not cache.',
51 'list-order' => array(
52 'desc' => 'List Order.',
59 'desc' => 'Only a specific table, eg. -t Netsuite_SalesOrder',
65 'desc' => 'Only a specific year, eg. -y 2009',
72 'desc' => "Get a mapped value or 'ALL' to show everything",
80 'desc' => "Import only items matching this condition, eg. --where='ItemFulfillment_id=123'",
88 'desc' => "print_r the data for import (matching -w)",
101 $ff = HTML_FlexyFramework::get();
103 die("run form cli only");
109 var $tables = array();
114 function initOpts($opts)
120 if (empty($this->opts['source'])) {
121 $this->opts['source'] = __DIR__.'/data';
125 $sd = strtoupper(substr( HTML_FlexyFramework::get()->database, -2));
126 $this->department = $sd;
127 $os = $this->opts['source'] ;
128 $this->opts['source'] .= '/'. $sd;
130 //print_R($this->opts);exit;
132 if (!is_dir($this->opts['source'])) {
133 if (file_exists($os)) {
134 $this->opts['source'] = $os;
138 die("--source must be a directory {$this->opts['source']}\n");
145 var $department = 'UNKNOWN';
148 $uinfo = posix_getpwuid( posix_getuid () );
149 $user = $uinfo['name'];
151 $cd = ini_get('session.save_path') ."/xtuple{$this->department}-{$user}";
152 if (!file_exists($cd)) {
153 mkdir($cd, 0700, true);
161 function get($path, $opts)
163 ini_set('memory_limit', -1); // unlimited!
165 PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($this, 'onPearError'));
168 $this->initOpts($opts);
169 $cd = $this->cachedir();
171 if (!empty($opts['mapped']) && !empty($opts['table'])) {
172 require_once 'Pman/Xtuple/Migration/Base.php';
173 $b = new Pman_Xtuple_Migration_Base($this);
174 echo "Mapped value: ";
175 $this->map(preg_replace('/^Netsuite_/i', '',$opts['table']));
177 $opts['mapped'] == 'ALL' ?
178 $this->map[preg_replace('/^Netsuite_/i', '',$opts['table'])] :
180 preg_replace('/^Netsuite_/i', '',$opts['table']),
190 $nocache = $this->opts['no-cache'];
191 // print_R($this->deps); exit;
192 // push salesorderitem up the list..
193 $this->deps['Netsuite_Invoice'][] = 'Netsuite_SalesOrderItem';
194 //$this->deps['Netsuite_Invoice'][] = 'Netsuite_SalesOrder';
195 $this->deps['Netsuite_Rates'] = array('Netsuite_Currency');
196 $this->deps['Netsuite_VendorBill'] = array('Netsuite_Rates');
197 $this->deps['Netsuite_PurchaseOrder'] = array('Netsuite_Rates');
198 $this->deps['Netsuite_ItemReceipt'] = array('Netsuite_Rates');
199 $this->deps['Netsuite_JournalEntry'] = array('Netsuite_Rates');
200 $this->deps['Netsuite_VendorPayment'][] = 'Netsuite_VendorBill';
201 $this->deps['Netsuite_CustomerPayment'][] = 'Netsuite_CustomerRefund';
202 $this->deps['Netsuite_CustomerRefund'][] = 'Netsuite_CreditMemo';
204 // delay inventory transfer?
205 $this->deps['Netsuite_InventoryTransfer'][] = 'Netsuite_InventoryItemLocations';
206 // $this->deps['Netsuite_InventoryTransfer'][] = 'Netsuite_VendorBill';
207 // bump up customer payment
208 //$this->deps['Netsuite_VendorBill'][] = 'Netsuite_CustomerPayment';
209 $this->deps['Netsuite_Check'][] = 'Netsuite_VendorBill';
210 $this->deps['Netsuite_VendorReturnAuthorization'][] = 'Netsuite_VendorBill';
211 $this->deps['Netsuite_Check'][] = 'Netsuite_VendorBill';
212 $this->deps['Netsuite_VendorCredit'][] = 'Netsuite_VendorBill';
214 $this->deps['Netsuite_Transfer'] = array( 'Netsuite_JournalEntry');
215 $this->tables[] = 'Netsuite_Transfer';
217 $this->deps['Netsuite_Deposit'] = array( 'Netsuite_JournalEntry');
218 $this->tables[] = 'Netsuite_Deposit';
221 $this->deps['Netsuite_InventoryTransfer'][] = 'Netsuite_ItemReceipt';
223 $this->deps['Netsuite_InventoryAdjustment'][] = 'Netsuite_ItemReceipt';
225 // locations have to have customers.
226 $this->deps['Netsuite_Location'][] = 'Netsuite_Customer';
227 $this->deps['Netsuite_InventoryItem'][] = 'Netsuite_Location';
228 $this->deps['Netsuite_ItemReceipt'][] = 'Netsuite_InventoryItem';
229 $this->deps['Netsuite_PurchaseOrder'][] = 'Netsuite_InventoryItem';
230 $this->deps['Netsuite_VendorBill'][] = 'Netsuite_InventoryItem';
234 //$this->deps['Netsuite_VendorBill'][] = 'Netsuite_Location';
236 // remove me later...- when elliot has finished it.
237 //$this->deps['Netsuite_ExpenseReport'][] = 'Netsuite_CustomerPayment';
239 //$this->deps['Netsuite_CreditMemo'] = array('Netsuite_InventoryItem');
241 array_unshift($this->tables, 'Netsuite_Rates');
242 $this->tables = $this->sortDeps($this->tables);
244 /// append Netsuite_ if doesn't already exist
245 if (empty($this->opts['table'])) {
247 } else if(is_string($this->opts['table'])) {
248 $this->opts['table'] = (strrpos($this->opts['table'], 'Netsuite_') !== false) ?
249 $this->opts['table'] : 'Netsuite_'.$this->opts['table'];
250 } else if (is_array($this->opts['table'])) {
251 foreach($this->opts['table'] as &$t) {
252 $t = (strrpos($t, 'Netsuite_') !== false) ? $t : 'Netsuite_'.$t;
256 if (!empty($this->opts['table']) && !in_array( $this->opts['table'], $this->tables)) {
257 die("Invalid table ! - {$this->opts['table']}\n");
262 //print_R($this->tables);
264 if ($this->opts['list-order']) {
265 echo implode("\n", $this->tables) ."\n";exit;
269 /// make sure all our accounting periods are open...
271 $per = DB_DataObject::Factory('period');
272 $per->query('UPDATE period set period_closed=false');
275 // remove invalid taxzones..
277 $per->query("delete from taxass where taxass_taxzone_id = (
278 SELECT taxzone_id FROM taxzone where taxzone_descrip='State Sales Tax Authority'
281 $per->query("delete from taxzone where taxzone_descrip='State Sales Tax Authority'");
284 if (!empty($this->opts['where'])) {
286 if (empty($this->opts['table'])) {
287 die("you must specify table if using --where\n");
290 list($wkey, $wval) = explode('=', $this->opts['where']);
293 $y = $this->opts['year'];
297 foreach($this->tables as $tbl) {
299 $n = preg_replace('/^Netsuite_/i', '', $tbl);
302 if ($n == 'TransactionApply') {
303 // we actually use it for all the transaction based tables,
304 // and our dump code now merges it into the parent transactions.
312 // should we use map() for this?
313 $f = $this->opts['source'] ."/{$tbl}.sql.json";
314 $all = file_exists($f.'.all') ;
315 $f = $all ? $f.'.all' : $f.'.'.$y;
316 $yy = $all ? 'all' : $y;
317 echo "Migrating $tbl from $f\n";
320 if (empty($this->opts['table'])) {
328 foreach (array( 2008,2009,2010,2011,2012) as $yr) {
329 $f = $this->opts['source'] ."/{$tbl}.sql.json";
330 $cyr = $all ? 'all' : $yr;
331 $f = $all ? $f.'.all' : $f.'.'.$yr;
332 $cache = $cd ."/" .md5($f) . "-{$tbl}-{$cyr}.sql.cache";
334 $sourcetime = file_exists($f) ? filemtime($f) : 0;
337 if ($n == 'Transfer' || $n == 'Deposit') {
342 if (!$sourcetime && !$is_other) {
343 echo "SKIP FILE - does not exists : $f\n";
348 $migtime = file_exists(dirname(__FILE__) ."/Migration/{$n}.php") ?
349 filemtime(dirname(__FILE__) ."/Migration/{$n}.php") : 0;
350 $cachetime = file_exists($cache) && filesize($cache) ? filemtime($cache) : 0;
352 print_R(array( $f=> $sourcetime, 'PHP'=>$migtime,$cache => $cachetime));
353 if ($cachetime >= $sourcetime && $cachetime >= $migtime) {
354 echo "SKIP FILE UP TO DATE? $tbl : $yr \n";
358 echo "SKIP TABLE - No convertor exists $tbl : $yr \n";
365 $this->runExport($tbl);
372 echo "RUNNING AS CACHE MISSING / OUT OF DATE: $cache \n";
373 $cmd = implode(' ', array(
374 '/usr/bin/php', // php
375 $_SERVER['SCRIPT_NAME'], // hk.php
379 '-f ' . $this->opts['source']
381 echo "\n\n-------------------------\n$cmd\n-----------------\n\n";
383 passthru( $cmd , $ret );
401 $cache = $cd ."/" .md5($f) . "-{$tbl}-{$yy}.sql.cache";
404 if ($n == 'Transfer' || $n == 'Deposit') {
405 if (!empty($this->opts['table']) && $this->opts['table'] != $tbl) {
409 if (file_exists($cache)) {
412 //DB_DataObject::DebugLevel(1);
413 $cl = $this->factory($n);
417 echo "Completed ". $this->opts['table'] . "\n";
427 if (!file_exists($f)) {
428 $this->updateReport($n, "Skipped - no dump exists ");
429 echo "Skip $n - no dump exists $f \n";
430 echo "creating empty cache $cache\n";
432 file_put_contents($cache, serialize(array()));
434 if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) {
435 echo "Completed ". $this->opts['table'] . "\n";
441 //var_dump($this->opts);
443 if ($this->opts['debug']) {
445 DB_DataObject::debugLevel( $this->opts['debug'] );
448 $cl = $this->factory($n);
451 $this->updateReport($n, "Skipped - no migration tool exists");
452 if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) {
453 echo "Skipped - no migration tool exists ". $this->opts['table'] . "\n";
459 $doimport = empty($this->opts['table']) || $this->opts['table'] == $tbl;
461 $usecache = !$nocache;
462 if (!empty($this->opts['table'])) {
463 if ($this->opts['table'] == $tbl) {
470 $sourcetime = filemtime($f);
471 $migtime = filemtime(dirname(__FILE__) ."/Migration/{$n}.php");
472 $cachetime = file_exists($cache) && filesize($cache) ? filemtime($cache) : 0;
475 if ($usecache && $cachetime >= $sourcetime && $cachetime >= $migtime) {
479 //$doimport ? $cl->validateUsed((array)json_decode($data[1])) : false;
482 $this->updateReport($n, false);
485 echo "Migrating $tbl - using cache : $cache \n";
490 if ($nocache && $cachetime < $sourcetime) {
491 echo "Migrating $tbl - cache to old, or does not exist\n";
493 if ($nocache && $cachetime < $migtime) {
494 echo "Migrating $tbl - migration code was updated\n";
498 // finally do the migration..
501 $cl->validateOutputSupport();
505 $missing_dep = false;
506 foreach( $this->deps[$tbl] as $dep) {
507 if (in_array($dep, $done)) {
510 echo "Checking for dep $dep\n";
511 if ($this->map(preg_replace('/^Netsuite_/', '', $dep))) {
522 if ($missing_dep !== false) {
523 echo "Skip $n - dependant table $missing_dep has not been done yet.\n";
524 $this->updateReport($n, "Skip dependant table $missing_dep has not been done yet.");
526 if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) {
527 echo "Failed ". $this->opts['table'] . "\n";
528 exit(3); // error condtion..
534 echo "Skip $n - no specified by --table .\n";
535 $this->updateReport($n, false);
536 if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) {
537 echo "Completed ". $this->opts['table'] . "\n";
547 $cl->validateUsed((array)json_decode($data[1]));
549 $this->map[$n] = array();
550 $total = count($data);
551 echo "Migrating $total Records\n";
553 foreach($data as $i=> $l) {
556 if (empty($cl->def)) { // first line is default.s
560 // print out some progress.
562 echo floor(($i/$total) * 100)."%\n";
565 $row = (array)json_decode($l);
567 // testing a single entry..
569 if (!isset($row[$wkey])) {
570 die("--where condition is invalid, key '$wkey' does not exist\n");
572 if ($row[$wkey] != $wval) {
575 if (!empty($opts['print'])) {
578 //DB_DataObject::debugLevel(1);
584 $add = $cl->migrate($row);
586 echo "Migrate return\n";
589 // rates does not have an id col.
590 if ($add !== false ) {
591 //echo "SAVEMAP {$row['id']} : $add \n";
592 $cl->saveMap(isset($row['id']) ? $row['id'] : $i, $add);
595 // free up some memory..
596 $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array();
597 $GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array();
606 //print_R(array_keys($this->map));
607 echo "Migrating $tbl - ". count(array_keys($this->map[$n])) . " Records added\n";
614 die("ONLY RUNNING IN TESTING MODE with
615 --where condition exiting before any damage is done..\n");
619 $str = $cl->resultsToString();
621 $this->updateReport($n, $cl->resultsToString());
625 if ($str !== false) {
626 file_put_contents($cd ."/{$tbl}-{$yy}.results.txt", $str);
627 echo "RESULTS IN {$cd}/{$tbl}-{$yy}.results.txt\n";
629 // sorting is pretty irrelivant, and screws things up horriably anyway.
630 //ksort($this->map[$n],SORT_STRING);
631 //var_dump($this->map[$n][3263]);
634 file_put_contents($cache, serialize($this->map[$n]));
635 echo "CACHED DATA IN php -r 'print_r(unserialize(file_get_contents(\"{$cache}\")));'\n";
636 touch($cache, max($sourcetime, $migtime, filemtime($cache)));
639 if (!empty($this->opts['table']) && $this->opts['table'] == $tbl) {
641 //print_r($this->map[$n]);
643 //var_dump($this->map[$n][3263]);
645 echo "Completed ". $this->opts['table'] . "\n";
648 die("\nRun again - to import more\n");
655 function updateReport($n, $res)
657 $cd = $this->cachedir();
659 $f = $this->opts['source'] ."/Netsuite_{$n}.sql.json";
661 // we need to update individual reports, and a 'merged one'..
663 if ($res !== false) {
664 file_put_contents( "{$cd}/" .md5($f) . "-Netsuite_{$n}.results.txt", $res);
666 $res = file_exists("{$cd}/" .md5($f) . "-Netsuite_{$n}.results.txt") ?
667 file_get_contents("{$cd}/" .md5($f) . "-Netsuite_{$n}.results.txt") :
668 "No results available";
671 static $latest = false;
673 $latest = "{$cd}/latest.results.txt";
674 file_put_contents($latest, "Import Report - " . date("Y-m-d H:i:s"). "\n\n");
676 $fh = fopen($latest, 'a');
678 fwrite($fh, "RESULTS FOR: $n:\n");
679 fwrite($fh, "$res\n\n");
693 $ini = parse_ini_file(dirname(__FILE__).'/Migration/netsuite.links.ini', true);
695 foreach($ini as $tbl => $maps) {
696 if (!in_array($tbl, $this->tables)) {
697 $this->tables[] = $tbl;
698 $deps[$tbl] = array();
701 foreach($maps as $col=>$to) {
702 $kv = explode(':', $to);
704 if (!in_array($kv[0], $this->tables)) {
705 $this->tables[] = $kv[0];
706 $deps[$kv[0]] = array();
709 if (in_array($kv[0], $deps[$tbl])) {
712 $deps[$tbl][] = $kv[0];
724 * has the map been loaded..
726 var $loaded = array();
731 if (isset($this->loaded[$n])) {
734 $this->loaded[$n] = true;
736 echo "RESET MAP FOR $n\n";
737 $this->map[$n] = array();
738 $cd = $this->cachedir();
740 $years = array('all', 2008,2009,2010,2011,2012);
742 $tbl = 'Netsuite_'. $n;
743 // make sure teh map is loaded for a class..
745 foreach($years as $yy) {
748 $f = $this->opts['source'] ."/{$tbl}.sql.json.". $yy;
750 $cache = $cd ."/" .md5($f) . "-{$tbl}-{$yy}.sql.cache";
752 if (!file_exists($cache) || !filesize($cache)) {
753 echo "NO CACHE $cache\n";
758 echo "LOADED CACHE $cache\n";
759 $this->map[$n] += unserialize(file_get_contents($cache));
765 unset($this->map[$n]);
770 function sortDeps($ar)
772 // this could be done by usort, but we need to build a reverse dependancy list as well.
774 // loop through an array,
775 // if we can add an item - add it, otherwise push it to the end..
779 for($i = 0 ; $i < count($ar); $i++ )
785 if (isset($ltest[$t])) {
786 echo "DEPENDANCIE FAILED FOR $t";
790 // no dependancies.. it can be added..
791 if (empty($this->deps[$t])) {
796 // have all the dependancies been added..
798 foreach($this->deps[$t] as $dep) {
799 if (!isset($done[$dep])) {
808 // a dependancy is missing..
809 // move this element to the end..
812 $ar = array_values($ar); // renumber...
828 $parts = explode('/', $n);
830 $cf = dirname(__FILE__) ."/Migration/{$n}.php";
831 $cn = "Pman_Xtuple_Migration_{$n}";
833 if (!file_exists($cf)) {
834 echo "Skip $n - no file '$cf' exists yet\n";
838 if (!class_exists($cn)) {
839 echo "SKIP class does not exist in file: $cn\n";
842 $cl = new $cn($this, isset($parts[1]) ? $parts[1] : false);
848 function onPearError($err)
851 $out = $err->toString();
854 //print_R($bt); exit;
856 foreach($err->backtrace as $b) {
857 $ret[] = $b['file'] . '(' . $b['line'] . ')@' . @$bt['class'] . '::' . @$bt['function'];
859 //convert the huge backtrace into something that is readable..
860 $out .= "\n" . implode("\n", $ret);
865 echo "\n-- FAILED --\n";
871 function runExport($tbl)
873 echo "RUNNING EXPORT to ensure data is up-to-date: $tbl \n";
874 $country = strtoupper($this->department);
875 // this is in Pman.Xtuple.Migrate directory now..
876 $cmd = implode(' ', array(
877 '/usr/bin/php', // php
878 __DIR__ . '/../web.Netsuite/index.php',
883 echo "\n\n-------------------------\n$cmd\n-----------------\n\n";
886 passthru( $cmd , $ret );