Fix #7123 - getting abra ready to test
[Pman.Xtuple] / DataObjects / Shiphead.php
1 <?php
2 /**
3  * Table Definition for shiphead
4  *
5  * States:
6  *    draft:
7  *      shipdate = filled in..
8  *      shipped: false
9  *    confirmed
10  *      shipdate : filled in
11  *      shipped : true
12  *    unconfirmed :
13  *      shipdate : filled in
14  *      shipped : false
15  *    void :
16  *      shipdate NULL
17  *      shipped : true
18  *
19  *
20  
21  */
22 require_once 'DB/DataObject.php';
23
24 class Pman_Xtuple_DataObjects_Shiphead extends DB_DataObject 
25 {
26     ###START_AUTOCODE
27     /* the code below is auto generated do not remove the above tag */
28
29     public $__table = 'shiphead';            // table name
30     public $shiphead_id;                     // int4(4)  not_null default_nextval%28shiphead_shiphead_id_seq%29 primary_key
31     public $shiphead_order_id;               // int4(4)  not_null
32     public $shiphead_order_type;             // text(-1)  not_null
33     public $shiphead_number;                 // text(-1)  not_null unique_key
34     public $shiphead_shipvia;                // text(-1)  
35     public $shiphead_freight;                // numeric(-1)  not_null default_0.0
36     public $shiphead_freight_curr_id;        // int4(4)  not_null default_basecurrid%28%29
37     public $shiphead_notes;                  // text(-1)  
38     public $shiphead_shipped;                // bool(1)  not_null default_false
39     public $shiphead_shipdate;               // date(4)  
40     public $shiphead_shipchrg_id;            // int4(4)  
41     public $shiphead_shipform_id;            // int4(4)  
42     public $shiphead_sfstatus;               // bpchar(-1)  not_null
43     public $shiphead_tracknum;               // text(-1)  
44     public $shiphead_location_id;               // text(-1)  
45     public $shiphead_shipto_id;               // text(-1)  
46     public $shiphead_delivery_note;          // TEXT
47
48     
49    /**
50     * Getter / Setter for $shiphead_freight_curr_id
51     *
52     * @param    mixed   (optional) value to assign
53     * @access   public
54     */
55     public function freight_curr() {
56         return func_num_args() ? $this->link('shiphead_freight_curr_id', func_get_arg(0)) : $this->link('shiphead_freight_curr_id');
57     }
58
59    /**
60     * Getter / Setter for $shiphead_shipchrg_id
61     *
62     * @param    mixed   (optional) value to assign
63     * @access   public
64     */
65     public function shipchrg() {
66         return func_num_args() ? $this->link('shiphead_shipchrg_id', func_get_arg(0)) : $this->link('shiphead_shipchrg_id');
67     }
68
69    /**
70     * Getter / Setter for $shiphead_shipform_id
71     *
72     * @param    mixed   (optional) value to assign
73     * @access   public
74     */
75     public function shipform() {
76         return func_num_args() ? $this->link('shiphead_shipform_id', func_get_arg(0)) : $this->link('shiphead_shipform_id');
77     }
78
79
80     /* the code above is auto generated do not remove the tag below */
81     ###END_AUTOCODE
82     
83     
84     
85     public function location() {
86         if (func_num_args()) {
87             $obj = func_get_args(0);
88             $this->shiphead_location_id = is_object($obj) ? $obj->pid() : $obj;
89             return true;
90         }
91         //echo '<PRE>';print_r($this);exit;
92         $l = DB_DataObject::Factory('location');
93         $l->get($this->shiphead_location_id);
94         return $l;
95         
96         
97     }
98      public function shipto() {
99         if (func_num_args()) {
100             $obj = func_get_args(0);
101             $this->shiphead_shipto_id = is_object($obj) ? $obj->pid() : $obj;
102             return true;
103         }
104         $l = DB_DataObject::Factory('shiptoinfo');
105         $l->get($this->shiphead_shipto_id);
106         return $l;
107         
108         
109     }
110     
111     function order()
112     {
113         if (func_num_args()) {
114             $ar = func_get_arg(0);
115             $this->shiphead_order_id = is_object($ar) ? $ar->pid() : $ar;
116             return $this->shiphead_order_id;
117         }
118         // no arguemnts - fetch..
119         static $cache = array();
120         if (isset($cache[$this->shiphead_order_id])) {
121             return $cache[$this->shiphead_order_id];
122         }
123             // technically check --- shiphead_order_type
124         $d = $this->factory('cohead');
125         if (!$d->get($this->shiphead_order_id)) {
126             $d = $this->factory('cohead');
127             $cache[$this->shiphead_order_id] = $d;
128             return $d;
129         }
130         $cache[$this->shiphead_order_id] = $d;
131         return $d;
132     
133     }
134     // returns a map of orderitemid-> shipitem.
135     // it assumes that a order item does not have multiple lines referenced..
136     function items()
137     {
138         $o = $this->order();
139         $ids = $o->items('coitem_id');
140         $i = $this->factory('shipitem');
141         $i->shipitem_shiphead_id = $this->shiphead_id;
142         $i->whereAddIn('shipitem_orderitem_id', $ids, 'int');
143         
144         $ret = array();
145         $i->find();
146         while($i->fetch()) {
147             $ret[$i->shipitem_orderitem_id] = clone($i);
148         }
149         return $ret;
150         
151         
152     }
153      
154     
155     
156     
157     function applyFilters($q, $au, $roo)
158     {
159         
160         if (!empty($q['_download'])) {
161             if (!$this->get($q['_download'])) {
162                 $roo->jerr("invalid id");
163             }
164             return $this->download($roo);
165         }
166         if (!empty($q['_stash'])) {
167             return $this->stashList($q['_stash'],$roo);
168         }
169         
170         
171         
172         if (!empty($q['_status'])) {
173             switch($q['_status']) {
174                 case 'S': //    [ 'S', "Shipped"],
175                     $this->whereAdd('shiphead_shipdate IS NOT NULL');
176                     
177                     break;
178                     
179                     
180                 case  'R' : // , "Reserved"],
181                     $this->whereAdd('shiphead_shipdate IS NULL AND  NOT shiphead_shipped');
182                     break;
183                     
184                 case  'V' : // , "Void Only"],
185                      $this->whereAdd('shiphead_shipdate IS NULL AND  shiphead_shipped');
186                     break;
187                 
188                 case  'A': //  "All"]
189                     break;
190                 default:
191                     
192             }
193         }
194         if (!empty($q['search']['fromDate'])) {
195             $v = $this->escape($q['search']['fromDate']);
196             $this->whereAdd("shiphead_shipdate >= '$v'");
197         }
198         if (!empty($q['search']['toDate'])) {
199             $v = $this->escape($q['search']['toDate']);
200             $this->whereAdd("shiphead_shipdate <= '$v'");
201         }
202         
203     
204
205         
206         
207         
208         
209         $this->selectAdd("
210             (SELECT count(shipitem_qty) FROM shipitem WHERE shipitem_shiphead_id = shiphead_id)  AS shipqty
211         ");
212         //DB_DataObject::debugLevel(1);
213         /// generally...
214         $cohead = $this->factory('cohead');
215         $cohead->joinAdd($this->factory('custinfo'), 'LEFT');
216         
217         $this->joinAdd(array('shiphead_order_id', $cohead, 'cohead_id'), 'LEFT');
218        
219         
220         $this->selectAs($this->factory('cohead'), 'shiphead_order_id_%s');
221         $this->selectAs($this->factory('custinfo'), 'shiphead_custinfo_%s');
222         $this->_extra_cols = array ( 'shiphead_order_id_cohead_number',  ); // sortable
223         
224         if (!empty($q['shiphead_custinfo_cust_id'])) {
225             $this->whereAdd('custinfo.cust_id = ' . ((int) $q['shiphead_custinfo_cust_id']));
226         }
227         
228         
229         
230         
231     }
232     
233     
234   
235     
236     function beforeInsert($q, $roo)
237     {
238         
239         //check to see if there are any other 'draft' shipments...
240         
241         $sh = DB_DataObject::Factory('shiphead');
242         $sh->shiphead_order_id = $this->shiphead_order_id;
243         $sh->shiphead_shipped  = false;
244         $sh->whereAdd('shiphead_shipdate IS NOT NULL');
245         if ($sh->count()) {
246             $roo->jerr("Order already contains a draft shipment");
247         }
248         
249         /*
250          *  check to see if the delivery notes is allow blank
251          *  if the shiphead location id is same as the default_location, then all blank, otherwise, MUST fill it in...
252          */
253         if(empty($this->shiphead_delivery_note)){
254             $l = DB_DataObject::factory('location');
255             $l = $l->defaultConfigLocation();
256             
257             if($this->shiphead_location_id != $l->pid()){
258                 $roo->jerr("You must fill in the delivery notes!");
259             }
260             
261         }
262         
263         
264         
265         if (empty($q['shiphead_number'] ) || $q['shiphead_number'] == 'Automatic') {
266             $this->shiphead_number = $this->genNumber();
267         }
268         
269         if (empty($this->shiphead_order_type)) {
270             $this->shiphead_order_type = 'SO';
271         }
272         
273         
274         
275     }
276     
277     function beforeUpdate($old,$q,$roo)
278     {
279         if(!empty($q['_send_delivery'])){
280             $this->setDelivery($roo);
281             $roo->jok("SENT");
282         }
283         
284         if (!empty($q['_void'])) {
285             
286             $this->factory('cohead')->lockTables();
287             if ($this->shiphead_shipped) {
288                 
289                 $res = $this->unconfirm($roo);
290                 if ($res !== true) {
291                     if ($res == -4) {
292                         $roo->jerr("You need to unpost the invoice for this sales order");
293                     }
294                     $roo->jerr("recall returned ". serialize($res));
295                 }
296                 
297                 $roo->jok("unconfirmed");
298             }
299             // result?
300             
301              
302             
303             $ret = $this->void($roo);
304             $ret === true ? $roo->jok("Voided") : $roo->jerr($ret);
305         }
306         
307         /*
308          *  check to see if the delivery notes is allow blank
309          *  if the shiphead location id is same as the default_location, then all blank, otherwise, MUST fill it in...
310          */
311         if(empty($this->shiphead_delivery_note)){
312             $l = DB_DataObject::factory('location');
313             $l = $l->defaultConfigLocation();
314             
315             if($this->shiphead_location_id != $l->pid()){
316                 $roo->jerr("You must fill in the delivery notes!");
317             }
318             
319         }
320         
321         if ($this->shiphead_shipped) {
322             $roo->jerr("You can not modified confirmed shipments - void it");
323         }
324         
325     }
326     
327     function genNumber()
328     {
329         $o = $this->order();
330         
331         $sh = DB_DataObject::factory('shiphead');
332         $sh->shiphead_order_id = $this->shiphead_order_id;
333
334         $sh->whereAdd("shiphead_number like '" . $this->escape($o->cohead_number) . "-%'");
335         $sh->selectAdd('substr(shiphead_number, ' . strlen($o->cohead_number.'--') .')::numeric as shiphead_number_seq');
336         $sh->orderBy('shiphead_number_seq DESC');
337         if (!$sh->count()) {
338             return $o->cohead_number .'-1';
339         }
340
341         $sh->find(true);
342         $n = array_pop(explode('-',$sh->shiphead_number)) * 1;
343         return $o->cohead_number .'-' . ($n+1);
344
345     }
346     
347     
348     function unconfirm($roo)
349     {
350         
351         
352         $s = $this->factory('shiphead');
353         if (empty($roo->transObj)) {
354             $s->query("BEGIN");
355         }
356         //DB_DataObject::debugLevel(1);
357         
358         $items = $this->items();
359         if (!$items) {
360             // increase our revision.
361             $this->query("UPDATE shiphead
362                     SET
363                         shiphead_rev = shiphead_rev +1,
364                         shiphead_shipped=FALSE
365                     WHERE
366                         shiphead_id = {$this->shiphead_id}
367             ");
368             return true;
369         
370         }
371         
372         $gl= DB_DataObject::Factory('gltrans');
373         $gl->selectAdd();
374         $gl->selectAdd('max(gltrans_id) as gltrans_id');
375         $gl->find(true);
376         $glbefore = $gl->gltrans_id;
377         // now do the actuall recall..
378         $dt = date('Y-m-d', strtotime( $this->shiphead_shipdate));
379         $s->query("SELECT recallShipment({$this->pid()}, '$dt') as res");
380         $s->fetch();
381         
382         
383         // recall shipment returns null if it's a sales order...
384         if (!empty($s->res) && $s->res < 0) {
385             
386             if (empty($roo->transObj)) {
387                $s->query("ROLLBACK");
388             }   
389             return $s->res;
390             
391         }
392         
393         $gl= DB_DataObject::Factory('gltrans');
394         $gl->selectAdd();
395         $gl->selectAdd('max(gltrans_id) as gltrans_id');
396         $gl->find(true);
397         
398         if ($glbefore == $gl->gltrans_id) {
399             $roo->jerr("no gltrans was commited for recal shipment?");
400         }
401         
402         
403         
404         // hopefully the fact that we have recalled this shipment will not break this..
405         
406         $our_site = substr($this->database(), -2);
407         
408         
409         // where are we transfering to...
410         $loc = DB_DataObject::Factory('location');
411         if (!$this->shiphead_location_id || !$loc->get($this->shiphead_location_id)) {
412             $roo->jerr("no location specified");
413         }
414         $target = $our_site;
415         
416         if ($loc->location_cust_id) {
417             $cust = $loc->customer();
418             if (!$cust) {
419                 $roo->jerr("the location specified does not have a customer associated with it. X");
420             }
421             
422             $target = $cust->char('INTERNALCOMPANY');
423             if (empty($target)) {
424                 $target = $our_site;
425             }
426         }
427             
428             // who owns the target location
429         
430         
431         
432         // migration process can not transfer.
433         if (HTML_FlexyFramework::get()->cli && $target != $our_site) {
434             $roo->jerr("invalid transfer via cli");
435             
436         }
437         
438         
439         
440         // transfer if necessary..
441         if ($target != $our_site) {
442             $res  = $this->revertStockTransfer($roo);
443             if (true !== $res) {
444                 // rollback handled by top level..
445                 //$s->query("ROLLBACK");
446                 
447                 return $res;
448             }
449             
450         }
451         
452         // checck for transfer - it should really look up customers with that remote, and see how many there are..
453         
454       
455         
456         // increase our revision.
457         $this->query("UPDATE shiphead
458                     SET
459                         shiphead_rev = shiphead_rev +1
460                     WHERE
461                         shiphead_id = {$this->shiphead_id}
462         ");
463         
464         if (empty($roo->transObj)) {
465             $s->query("COMMIT");
466         }
467         return true;    
468     }
469     
470     function confirm($roo)
471     {
472         $items = $this->items();
473         if (!$items) {
474             // increase our revision.
475             $roo->jerr("shipment does not have any items");
476             return true;
477         
478         }
479          
480         $s = $this->factory('shiphead');
481          if (empty($roo->transObj)) {
482             $s->query("BEGIN");
483         }
484         $dt = date('Y-m-d', strtotime( $this->shiphead_shipdate));
485         $s->query("SELECT shipShipment({$this->pid()}, '$dt') as res");
486         $s->fetch();
487         
488         if (!is_null($s->res)) {
489             if (empty($roo->transObj)) {
490                 $s->query("ROLLBACK");
491             }
492            
493             $roo->jerr("ship returned ". serialize($s->res));
494         }
495         
496         $thisdb = substr($this->database(), -2);
497         
498         // migration process can not transfer.
499         
500         
501         // determine the location.
502         $loc = DB_DataObject::Factory('location');
503         if (!$loc->get($this->shiphead_location_id)) {
504             $roo->jerr("no location specified");
505         }
506         
507         
508         $cust = $loc->customer();
509         
510         if (!$cust) {
511             $roo->jerr("the location specified does not have a customer associated with it. X");
512         }
513         $location_db = $cust->char('INTERNALCOMPANY');
514         $location_db  = empty($location_db ) ? $thisdb : $location_db ;
515         
516         
517         $is_cli = HTML_FlexyFramework::get()->cli;
518         
519         if (HTML_FlexyFramework::get()->cli && ($thisdb != $location_db)) {  
520             $roo->jerr("cli can not do a transfer");
521         }
522         
523         // transfer if necessary..
524         if ($thisdb != $location_db) {
525             
526             //$roo->jerr("Stock transfer disabled at present $thisdb to $location_db");
527             $res  = $this->doStockTransfer($roo);
528             if (true !== $res) {
529                 // rollback handled by top level..
530                 //$s->query("ROLLBACK");
531                 
532                 return $res;
533             }
534             
535         }
536         // 
537         
538         
539         
540         // result?
541         if (empty($roo->transObj)) {
542             $s->query("COMMIT");
543         }
544         return true;
545         
546         
547          
548     }
549     
550     
551     function onInsert($q, $roo)
552     {
553         
554         if (isset($q['shipitems'])) {
555             $this->factory('cohead')->lockTables();
556             $this->updateItems(json_decode($q['shipitems']),$roo);
557         }
558         
559         
560     }
561     function onUpdate($old, $q, $roo)
562     {
563         if (isset($q['shipitems'])) {
564             $this->factory('cohead')->lockTables();
565             $this->updateItems(json_decode($q['shipitems']), $roo);
566         }
567         if (isset($q['_confirm'])) {
568             $this->factory('cohead')->lockTables();
569             $res = $this->confirm($roo);
570             if (true !== $res) {
571                 $roo->jerr($res);
572             }
573         }
574         
575     }
576     
577     function void($roo)
578     {
579         // if it's void then
580         // shiphead_shipped = true and shipdate is empty..
581         if (!empty($this->shiphead_shipped) && empty($this->shiphead_shipdate) ) {
582             $roo->jerr("the shipment is already void");
583         }
584         $this->stashShipment();
585         
586         $old = $this->items();
587         
588         // always attempt to reverse it..
589         // the reverse code should delete the shipitem line - causing this to
590         // do nothing the next time round..
591         foreach($old as $id => $ol) {
592             $ol->reverse($roo);
593         }
594        
595         
596          if (!$old && empty($this->shiphead_shipdate)) {
597             return "Already void";
598         }
599        
600         $o = clone($this);
601         $this->shiphead_shipdate = $this->sqlValue('NULL');
602         $this->shiphead_shipped = true;
603         $this->update($o);
604         
605         // we have to flag it as shipped..
606         
607         
608         
609         return true;
610         
611         
612     }
613     /**
614      * update Items in a shipment
615      *  array :
616      *     order_item_id => qty
617      *  or
618      *  
619      *     n =>  array(
620      *              shipitem_orderitem_id => order_item_id
621                     shipitem_qty => qty
622      )
623      *
624      *
625      *
626      */
627     
628     
629     function updateItems($ar, $roo)
630     {
631         //print_r($ar);
632         //DB_DataObject::debugLevel(1);
633         // ar is 
634         //$o->shipitem_orderitem_id,
635         //$o->shipitem_qty,
636         
637        
638         $old = $this->items();
639         
640         $bar = array(); //$ar;
641         
642         // check stdcost of item to make sure it's not $0
643         $badItems = array();
644         
645         foreach ($ar as $order_item => $qty){
646             if (is_object($qty)) {
647                 $order_item = $qty->shipitem_orderitem_id;
648                 $qty = $qty->shipitem_qty;
649             }
650             
651             $oi = DB_DataObject::factory('coitem');
652             $oi->get($order_item);
653             $item = $oi->itemsite()->item();
654             
655             $i = DB_DataObject::factory('item');
656             $i->query("SELECT stdCost({$item->pid()}) AS cost");
657             $i->fetch();
658             
659             if($i->cost == 0){
660                 $badItems[] = $item->item_number;
661             }
662         }
663         
664         if(count($badItems)){
665             $roo->jerr("These items have 0 stdCost " . implode(', ', $badItems));
666         }
667         
668         // explode kits..
669         foreach($ar as $order_item=>$qty) {
670             
671             if (is_object($qty)) {
672                 $order_item = $qty->shipitem_orderitem_id;
673                 $qty = $qty->shipitem_qty;
674             }
675             // in theory what happens to kit parts being updated  to zero quantity
676             // I'm not sure it's even allowed..
677             //if (empty($qty )) {
678             //    $bar[$order_item] = $qty;
679             //    continue;
680             //}
681             
682             $oi = DB_DataObject::factory('coitem');
683             $oi->get($order_item);
684             if ($oi->itemsite()->item()->item_type != 'K') {
685                 $bar[$order_item] = $qty;
686                 continue;
687             }
688             // got a kit..
689             // find all the child elements..
690             $si = DB_DataObject::factory('coitem');
691             $si->coitem_linenumber =  $oi->coitem_linenumber;
692             $si->coitem_cohead_id   = $oi->coitem_cohead_id;
693             $si->whereAdd('coitem_subnumber != 0 ');
694             $si->find();
695             //echo "CONVERTING kit : {$oi->pid()}\n";
696             while ($si->fetch()) {
697                 //echo "ADDING kit : {$si->coitem_id}\n";
698                 $bar[$si->coitem_id] = floor($si->coitem_qtyord / $oi->coitem_qtyord) * $qty;
699             }
700             //$roo->jerr("fixing kit of parts at present");
701             
702         }
703         $ar = $bar;
704         
705                 
706         // check stock      
707         if(!empty($roo->bootLoader->Xtuple['prevent_negative'])){
708             $stock = array();
709             foreach ($ar as $order_item => $qty){
710                 if (is_object($qty)) {
711                     $order_item = $qty->shipitem_orderitem_id;
712                     $qty = $qty->shipitem_qty;
713                 }
714                 if(empty($qty)){
715                     continue;
716                 }
717
718                 $coitem = DB_DataObject::factory('coitem');
719                 $coitem->get($order_item);
720
721                 $balance = $coitem->itemsite()->checkLocationStock($this->shiphead_location_id);
722                 
723                 if(empty($balance) || $balance < $qty){
724                     $stock[] = $coitem->itemsite()->item()->item_number;
725                 }
726             }
727
728             if(count($stock)){
729                 $roo->jerr("These items have negative stock " . implode(', ', $stock));
730             }
731         }
732         
733         
734         foreach($ar as $order_item=>$qty) {
735             if (is_object($qty)) {
736                 $order_item = $qty->shipitem_orderitem_id;
737                 $qty = $qty->shipitem_qty;
738             }
739             
740             if (!isset($old[$order_item])) {
741                 if (empty($qty )) {
742                     continue; // skip empty lines..
743                 }
744                 $this->addShipitem($order_item, $qty );
745                 continue;
746             }
747             $ol = $old[$order_item];
748             
749             
750             
751             if ($ol->shipitem_qty == $qty) {
752                 unset($old[$order_item]);
753                 continue;
754             }
755             //$roo->jerr("REVERSING");
756             $ol->reverse($roo);
757             if (empty($qty )) {
758                 unset($old[$order_item]);
759                 continue;
760             }
761             
762             $this->addShipItem($order_item, $qty );
763             unset($old[$order_item]);
764         }
765         // $ar - should contain all the order items.. if it does not...
766         // then we should update the quatities.
767         foreach($old as $id => $ol) {
768             $ol->reverse($roo);
769         }
770         
771         
772     }
773     /**
774      * add  a ship item
775      *
776      * - this mimic's issueToshipping
777      * 
778      * @param int|Object $oid the orderitem id or orderitem dataobject
779      * @param number     $qty the quantity to add.. - there should only be one per order..
780      *
781      */
782     function addShipItem($oid, $qty)
783     {
784         $si = $this->factory('shipitem');
785         $o = $this->order();
786         
787         if (is_object($oid)) {
788             $oi = $oid;
789             $oid = $oi->pid();
790         } else { 
791             $oi = $this->factory('coitem');
792             $oi->get($oid);
793         }
794         // it's actually a
795         
796         $itemsite = DB_DataObject::factory('itemsite');
797         $itemsite->get('itemsite_id', $oi->coitem_itemsite_id);
798         if ($itemsite->itemsite_controlmethod !='R') {
799             // it will not get created..
800             return -1;
801             
802         }
803         $roo = HTML_FlexyFramework::get()->page;
804         if (!$itemsite->itemsite_stocked) {
805             $roo->jerr("Item ". $itemsite->item()->item_number . " is not marked as stocked, so can not be fullfilled.");
806             
807         }
808         if (!$itemsite->itemsite_loccntrl) {
809             $roo->jerr("Item ". $itemsite->item()->item_number . " is not marked as location controlled, so can not be fullfilled.");
810         }
811         
812          
813         $d = $this->factory($this->tablename());
814         
815       
816         if (empty($roo->transObj)) { 
817             $d->query("BEGIN");
818         }
819         
820         // verify the product is stocked...
821         
822         
823         
824         
825         
826         
827         /// issue to shipping can not handle multiple shipments for the same
828         // product!!!
829         $ci = $this->factory('coitem');
830         $ci->query("SELECT NEXTVAL('itemloc_series_seq')::integer as itemlocseries ");
831         $ci->fetch();
832         $itemlocSeries = $ci->itemlocseries;
833         
834         $ci = $this->factory('coitem');
835         $q = "SELECT postInvTrans(
836                 itemsite_id,
837                 'SH'::text,
838                 {$qty}::numeric ,
839                 'S/R'::text,
840                 'SO'::text,
841                 formatSoNumber(coitem_id),
842                 shiphead_number,
843                 ('Issue ' || item_number || ' to Shipping for customer ' || cohead_billtoname),
844                 getPrjAccntId(cohead_prj_id, costcat_shipasset_accnt_id), costcat_asset_accnt_id,
845                 $itemlocSeries,
846                 CAST('{$this->shiphead_shipdate}' AS timestamp with time zone)
847                 ) AS invhistid
848             FROM coitem, cohead, itemsite, item, costcat, shiphead
849                 WHERE ( (coitem_cohead_id=cohead_id)
850                  AND (coitem_itemsite_id=itemsite_id)
851                  AND (itemsite_item_id=item_id)
852                  AND (itemsite_costcat_id=costcat_id)
853                  AND (coitem_id={$oid})
854                  AND (shiphead_id={$this->shiphead_id}) )";
855         
856         
857         $ci->query($q);
858         $ci->fetch(true);
859         
860         if (empty($ci->invhistid) || $ci->invhistid < 0) {
861             
862             $roo->jerr("post inv trans failed " . @$ci->invhistid );
863             //echo $q; exit;
864         }
865         
866         $si->setFrom(array(
867             
868             'shipitem_orderitem_id' => $oid,
869             'shipitem_shiphead_id' => $this->shiphead_id,
870             'shipitem_qty'=> $qty,
871             'shipitem_shipped' =>false,
872             'shipitem_shipdate' => $this->shiphead_shipdate,
873             'shipitem_transdate' =>  $this->shiphead_shipdate, // $this->sqlValue('NOW()'),
874             //'shipitem_trans_username' text,
875             'shipitem_invoiced' => false,
876             //'shipitem_invcitem_id' => 
877             //'shipitem_value' => $oi->coitem_custprice * $qty,
878             'shipitem_invhist_id' => $ci->invhistid
879             
880             
881         ));
882         
883        // $si->shipitem_value = $si->sqlValue("
884        //         currtobase({$o->cohead_curr_id}, {$oi->coitem_custprice} * {$qty}, '{ $this->shiphead_shipdate}')
885        // ");
886         $si->shipitem_value = $si->sqlValue("
887                 (SELECT invhist_value_before - invhist_value_after FROM invhist where invhist_id = {$ci->invhistid} )
888         ");
889         $si->insert();
890          
891         
892         
893          $d = $this->factory($this->tablename());
894         $d->query("
895                   
896                     SELECT
897                             itemlocdist_id,
898                             itemlocdist_reqlotserial,
899                             itemlocdist_distlotserial,
900                             itemlocdist_qty,
901                             itemsite_loccntrl,
902                             itemsite_controlmethod,
903                             itemsite_perishable,
904                             itemsite_warrpurc,
905                             COALESCE(itemsite_lsseq_id,-1) AS itemsite_lsseq_id,
906                             COALESCE(itemlocdist_source_id,-1) AS itemlocdist_source_id
907                         FROM
908                             itemlocdist, itemsite
909                         WHERE
910                             ( (itemlocdist_itemsite_id=itemsite_id)
911                             AND
912                             (itemlocdist_series=$itemlocSeries) )
913                         ORDER BY itemlocdist_id;
914
915                   
916                    
917                 ");
918         $d->fetch();
919         
920         if (empty($d->itemlocdist_id)) {
921             $ssi = DB_DataObject::factory('shipitem');
922             $ssi->get($si->pid());
923             $roo->jerr("FAILED TO ADD ITEM " . $itemlocSeries. "\n".
924                 print_r($ssi->toArray(), true)                      
925                        );
926         }
927         
928         $itemlocdist_id = $d->itemlocdist_id;
929         /*
930         while ($d->fetch()) {
931             print_r($d->toArray('%s', true));
932         }
933        
934         //print_r($ildist->toArray('%s', true));exit;
935              */
936         
937         // why grab from the line item...
938         // it should grab from the shiphead?!?
939         
940         $location = $this->shiphead_location_id; /// where.... from..
941         
942            
943         $d = $this->factory($this->tablename());
944         $d->query("
945                   
946                 INSERT INTO
947                     itemlocdist (
948                         itemlocdist_itemlocdist_id, itemlocdist_source_type,
949                         itemlocdist_source_id,  itemlocdist_qty,
950                         itemlocdist_ls_id, itemlocdist_expiration
951                     )
952                     SELECT
953                         itemlocdist_id,       'L',
954                         {$location},        {$qty} * -1,
955                         itemlocdist_ls_id, endOfTime()
956                     FROM
957                         itemlocdist
958                     WHERE (itemlocdist_id= $itemlocdist_id);
959
960             ");
961                   
962                   
963                   
964                
965         
966         // this generates an additional itemlocdist record..
967         
968         $d = $this->factory($this->tablename());
969         $d->query("SELECT distributeToLocations({$itemlocdist_id}) AS result");
970         $d = $this->factory($this->tablename());
971         $d->query("SELECT postItemlocseries({$itemlocSeries}) AS result");
972         
973         $d = $this->factory($this->tablename());
974         
975         if (empty($roo->transObj)) { 
976
977             $d->query("COMMIT");
978         }
979         
980         
981          
982         
983         
984         
985         return $si->pid(); //we could return insert()...
986     }
987     
988     // dumping old versions..
989     function stashItems()
990     {
991         $i = $this->factory('shipitem');
992         $i->selectAdd();
993         $i->shipitem_shiphead_id = $this->pid();
994         $i->selectAdd("
995             (SELECT coitem_itemsite_id FROM coitem where coitem_id = shipitem_orderitem_id) AS itemsite_id,
996             shipitem_qty
997         ");
998         return $i->fetchAll('itemsite_id','shipitem_qty');
999         
1000     }
1001     
1002     
1003     function stashShipment()
1004     {
1005         
1006         $old = $this->stashItems();
1007         if (empty($old)) {
1008             return; // no point in stashing empty..
1009         }
1010         // we should stash based on id.. - so old ones can be deleted.
1011         // group into 100's.. eg.
1012         //  /shipstash/{floor(id/100)00/{id}/... versions..
1013         $ff = HTML_FlexyFramework::get();
1014         
1015         if (empty($ff->Xtuple['storedir'])) {
1016             $ff->page->jerr("Xtuple['storedir'] is not configured");
1017         }
1018         
1019         $fd = $ff->Xtuple['storedir'] .
1020             '/shipstash/' .
1021             floor($this->shiphead_order_id /100) .
1022             '/' . $this->shiphead_order_id .'/';
1023         
1024         
1025         if (!is_writable( $ff->Xtuple['storedir'])) {
1026             $ff->page->jerr("Xtuple['storedir'] is not writable");
1027             return;
1028         }
1029         
1030         if (!file_exists($fd)) {
1031             mkdir($fd,0700, true);
1032         }
1033         
1034         $d = time();
1035         while (true) {
1036             $fn = $fd.  $this->shiphead_number . '_' . date('Y-m-d h:i:s',$d). '.json';
1037             if (file_exists($fn)) {
1038                 $d++;
1039                 continue;
1040             }
1041             break;
1042         }
1043         
1044        
1045         
1046         file_put_contents($fn, json_encode( $old ));
1047         //$ff->page->jok("wrote stash");
1048         
1049     }
1050     function stashList($id, $roo)
1051     {
1052         $ff = HTML_FlexyFramework::get();
1053         
1054         $id = (int) $id;
1055         
1056            
1057         
1058         if (empty($ff->Xtuple['storedir'])) {
1059             $roo>jerr("Xtuple['storedir'] is not configured");
1060         }
1061         $fd = $ff->Xtuple['storedir'] .
1062             '/shipstash/' .
1063             floor($id /100) .
1064             '/' . $id;
1065             
1066         if (!file_exists($fd)) {
1067             $roo->jerr("no data available " . $fd);
1068             $roo->jdata(array());
1069         }
1070         
1071         
1072         
1073         $ret = array();
1074         foreach(scandir($fd) as $d) {
1075             if (!preg_match('/\.json$/', $d)) {
1076                 continue;
1077             }
1078             $n = preg_replace('/\.json$/', '', $d);
1079             $n = explode('_', $n);
1080             $n = $n[0] . ' voided on ' . $n[1];
1081             
1082             $ret[] = array(
1083                 
1084                 'name' => $n,
1085                 'data' => json_decode(file_get_contents($fd.'/'. $d))
1086             );
1087         }
1088         return $roo->jdata($ret);
1089         
1090         
1091     }
1092     /**
1093      * return a list of what should be transfered.
1094      *
1095      * 
1096      */
1097     function transferItems()
1098     {
1099         $i = $this->factory('shipitem');
1100         $i->selectAdd();
1101         $i->shipitem_shiphead_id = $this->pid();
1102         $i->selectAdd("
1103             (SELECT  item_number
1104                 FROM coitem
1105                 LEFT JOIN itemsite ON coitem_itemsite_id = itemsite_id
1106                 LEFT JOIN item ON itemsite_item_id = item_id
1107                 WHERE coitem_id = shipitem_orderitem_id
1108             ) AS item_number,
1109             shipitem_qty
1110         ");
1111         // only transfer products... ?? should be default... - code should not generate shipments for kits etc..
1112         /*$i->whereAdd("
1113             'P' = (SELECT   item_type
1114                 FROM coitem
1115                 LEFT JOIN itemsite ON coitem_itemsite_id = itemsite_id
1116                 LEFT JOIN item ON itemsite_item_id = item_id
1117                 WHERE coitem_id = shipitem_orderitem_id
1118             ) 
1119             
1120              
1121         ")*/
1122         return $i->fetchAll('item_number','shipitem_qty');
1123         
1124     }
1125     /**
1126      * return a list of source locations for this shipment - it should
1127      * only return 1 in our system...
1128      *
1129      */
1130      
1131      
1132     function sourceLocations()
1133     {
1134         // this is based on the location listed in the coitem
1135         $i = $this->factory('shipitem');
1136         $i->selectAdd();
1137         $i->shipitem_shiphead_id = $this->pid();
1138         $i->selectAdd("
1139             (SELECT  coitem_location_src
1140                 FROM coitem
1141                 WHERE coitem_id = shipitem_orderitem_id
1142             ) AS location_src
1143                       ");
1144         $src = array_unique($i->fetchAll('location_src'));
1145         return $src;
1146     }
1147     
1148     function doStockTransfer($roo)
1149     {
1150         $roo->sessionState(0); // non-locking!?
1151         $roo->jerr("stock transfer is disabled at present");
1152         
1153         
1154         // why credit HK?
1155         //if ($this->order()->cust()->cust_name == 'Bloom and Grow HK') {
1156         //    return $this->creditHK($roo);
1157         //}
1158        
1159        
1160         $srcs = $this->sourceLocations();
1161         if (count($srcs) != 1) {
1162             return "Shipment contains multiple source locations - this should not be allowed";
1163         }
1164         
1165        
1166         $loc = DB_DataObject::Factory('location');
1167         $loc->get($srcs[0]); 
1168         $cust = $loc->customer();
1169         
1170         $our_db = substr($this->database(),-2);
1171         $remote_db = $cust->char('INTERNALCOMPANY');
1172         if ($our_db == $remote_db) {
1173             $roo->jerr("stock transfer between same company - something is set up wrong.");
1174         }
1175         
1176         
1177         
1178         
1179         
1180         // create a purchase order...
1181         
1182         $po = DB_DataObject::factory('pohead');
1183         //Create purchase order + reciept + voucher.
1184         //createAllFromShipment($roo, $shiphead, $remote_db, $cust, $loc)
1185         $prices = $po->createAllFromShipment($roo, $this, $remote_db, $cust, $loc);
1186         
1187    
1188         
1189         // ensure we have a price list???
1190         
1191         
1192         
1193         $order = $this->order();
1194         $tx_items =  $this->transferItems();
1195         $items = array();
1196         $line = 0;
1197         foreach($tx_items as  $item=>$qty) {
1198             $line ++;
1199             
1200             $items[] = array(
1201                     
1202                 'linenumber' => $line,
1203                 'qty' => $qty,
1204                 'unitprice' => $prices[$item],
1205                 'item_number' => $item,
1206             );
1207             
1208         }
1209         
1210         
1211        
1212        
1213         
1214         
1215         // find the location it is comming from...
1216         
1217         
1218         
1219         
1220         
1221         
1222         // on hk side..
1223         //Needs to know 
1224         //  Order number (so it can be voided later..)
1225         //  list of items (and the source location)
1226         //  Qty
1227         //  UnitPrice?? - this should be the FIFO cost..
1228         
1229         
1230         // on the signapore site
1231         
1232         $rev = $this->shiphead_rev ?   '-rev' . $this->shiphead_rev  : '';
1233         //$roo->jerr($this->shiphead_rev);
1234         
1235          
1236         require_once 'HTTP/Request.php';
1237         
1238         $req = new HTTP_Request( "{http://localhost/$roo->rootURL/{$remote_db}.php/Roo/Invchead" );
1239         $req->setMethod(HTTP_REQUEST_METHOD_POST);
1240         $req->addPostData( array(
1241                 '_is_xfer' => $our_db,
1242                 'invchead_invcnumber' => 'XF-'.$this->shiphead_number . $rev,  // invoice should match number...
1243                 'invchead_orderdate' => $this->shiphead_shipdate,
1244                 'invchead_invcdate' => $this->shiphead_shipdate,
1245                 //'cust_number' => $ff->Xtuple['main_remote_cust_number'] , // $roo->jerr("Old code using remote_customer - disabled");
1246                 'invchead_posted' => false,
1247                 'invchead_printed' => false,
1248                 'invchead_commission' => 0,
1249                 'invchead_freight' => 0,
1250                 'invchead_misc_amount' => 0,
1251                 'invchead_shipchrg_id' => -1,
1252                 'items' => $items,
1253                 'src_location' => $loc->location_name
1254           
1255         ));
1256         
1257         $res = $req->sendRequest();
1258         if (is_a($res,'PEAR_Error')) {
1259             return "MAIN REQUEST RETURNED: ". $res->toString();
1260         }
1261         $res = json_decode($req->getResponseBody());
1262         
1263         
1264         
1265         if (!is_object($res)) {
1266             return "MAIN REQUEST RETURNED: ". $req->getResponseBody();
1267         }
1268         
1269         if (!$res->success) {
1270             return "MAIN REQUEST RETURNED: ". $res->errorMsg;
1271         }
1272          
1273         return true;
1274          
1275         
1276     }
1277     // invoicing HK for stock..
1278     
1279     
1280     function creditHK($roo)
1281     {
1282           
1283         $this->jerr("creditHK code - creates a credit memo on remote site? - why?");
1284         $ff = HTML_FlexyFramework::get();
1285         
1286         if (empty($ff->Xtuple['main_url'])) {
1287             $roo->jerr("Xtuple['main_url'] is not configured");
1288         }
1289         if (empty($ff->Xtuple['main_remote_cust_number'])) {
1290             //$roo->jerr( "Xtuple['main_remote_cust_number'] is not configured");
1291             $roo->jerr("Old code using remote_customer - disabled");
1292         }
1293         
1294         
1295         
1296          
1297         
1298         $order = $this->order();
1299         $tx_items =  $this->transferItems();
1300         $po = DB_DataObject::factory('pohead');
1301         $prices = $po->fetchXferPrices($roo, $tx_items);
1302             
1303         
1304         $items = array();
1305         $line = 0;
1306         foreach($tx_items as  $item=>$qty) {
1307             $line ++;
1308             $items[] = array(
1309                     
1310                 'linenumber' => $line,
1311                 'qty' => $qty,
1312                 'unitprice' => $prices[$item],
1313                 'item_number' => $item,
1314             );
1315             
1316         }
1317         
1318         
1319         $srcs = $this->sourceLocations();
1320         if (count($srcs) != 1) {
1321             return "Shipment contains multiple source locations - this should not be allowed";
1322             
1323         }
1324         $loc = DB_DataObject::Factory('location');
1325         $loc->get($srcs[0]); 
1326         
1327         
1328         
1329         
1330         
1331         // on hk side..
1332         //Needs to know 
1333         //  Order number (so it can be voided later..)
1334         //  list of items (and the source location)
1335         //  Qty
1336         //  UnitPrice?? - this should be the FIFO cost..
1337         
1338         
1339         // on the signapore site
1340         
1341         $full = DB_DataObject::Factory('cmhead');
1342         $full->autoJoin();
1343         $full->get($this->pid());
1344         
1345         $roo->jerr("Old code using remote_customer - disabled");
1346         
1347         require_once 'HTTP/Request.php';
1348         
1349         $req = new HTTP_Request( $ff->Xtuple['main_url'] . '/Roo/Cmhead' );
1350         $req->setMethod(HTTP_REQUEST_METHOD_POST);
1351                     
1352         $req->addPostData( array(
1353                 '_is_xfer' => 1,
1354                 
1355                 
1356                 'cmhead_number' => 'XF-INV-' . $this->shiphead_number,
1357                 'cust_number' => $ff->Xtuple['main_remote_cust_number'], // $roo->jerr("Old code using remote_customer - disabled");
1358                 'cmhead_docdate' => $this->shiphead_shipdate,
1359     
1360                 
1361                 'cmhead_freight' => 0 , //$full->cmhead_freight ,
1362                 'cmhead_misc' => 0, //$full->cmhead_misc,
1363             
1364                 'cmhead_comments' => $full->cmhead_comments,
1365                 //'cmhead_misc_descrip'=> '', //$full->cmhead_misc_descrip,
1366                 
1367                 'location_name' => $loc->location_name,
1368                 //'cmhead_misc_accnt_id_accnt_number' => $full->cmhead_misc_accnt_id_accnt_number,
1369                 
1370     
1371                 //'cmhead_curr_id;                  // int4(4)  default_basecurrid%28%29
1372                 //cmhead_freighttaxtype_id;        // int4(4)  
1373                 //public $cmhead_salesrep_id;              // int4(4)  
1374     
1375                 //cmhead_taxzone_id;               // int4(4)  
1376     
1377                  
1378                 'items' => $items,
1379                 
1380         ));
1381         
1382         $res = $req->sendRequest();
1383         if (is_a($res,'PEAR_Error')) {
1384             $roo->jerr( "MAIN REQUEST RETURNED: ". $res->toString());
1385         }
1386         $res = json_decode($req->getResponseBody());
1387         
1388         
1389         
1390         if (!is_object($res)) {
1391             $roo->jerr("MAIN REQUEST RETURNED: ". $req->getResponseBody());
1392         }
1393         
1394         if (!$res->success) {
1395             $roo->jerr("MAIN REQUEST RETURNED: ". $res->errorMsg);
1396         }
1397          
1398         return true;
1399     }
1400     
1401     
1402     
1403     function revertStockTransfer($roo)
1404     {
1405         $roo->jerr("stock transfer is disabled at present");
1406         if ($this->order()->cust()->cust_name == 'Bloom and Grow HK') {
1407             $roo->jerr("You can not reverse transfers to HK - you need to create
1408                        a credit memo for HK - which will move the stock back");
1409         }
1410         
1411         
1412         if (preg_match('/-d$/', $this->shiphead_number)) {
1413             // it's a migrated record.. no need to track stuff..
1414             return true;
1415         }
1416         
1417         $po = DB_DataObject::factory('pohead');
1418         //Create purchase order + reciept + voucher.
1419         $po->revertAllFromShipment($roo, $this);
1420         
1421         $ff = HTML_FlexyFramework::get();
1422         
1423         if (empty($ff->Xtuple['main_url'])) {
1424             return "Xtuple['main_url'] is not configured";
1425         }
1426         //if (empty($ff->Xtuple['main_remote_cust_number'])) {
1427         //    return "Xtuple['main_remote_cust_number'] is not configured";
1428         //}
1429         
1430         
1431         $order = $this->order();
1432          
1433         
1434         
1435         // on hk side..
1436         //Needs to know 
1437         //  Order number (so it can be voided later..)
1438         //  list of items (and the source location)
1439         //  Qty
1440         //  UnitPrice?? - this should be the FIFO cost..
1441         
1442         
1443         // on the signapore site
1444         
1445         $rev = $this->shiphead_rev ?  '-rev' . $this->shiphead_rev  : '';
1446         
1447         require_once 'HTTP/Request.php';
1448         
1449         $req = new HTTP_Request( $ff->Xtuple['main_url'] . '/Roo/Invchead' );
1450         $req->setMethod(HTTP_REQUEST_METHOD_POST);
1451         $req->addPostData( array(
1452                 '_is_xfer_void' => 1,
1453                 'invchead_invcnumber' => 'XF-'.$this->shiphead_number . $rev
1454         ));
1455         
1456         $res = $req->sendRequest();
1457         if (is_a($res,'PEAR_Error')) {
1458             return "MAIN REQUEST RETURNED: ". $res->toString();
1459         }
1460         $res = json_decode($req->getResponseBody());
1461         
1462         
1463         
1464         if (!is_object($res)) {
1465             return "MAIN REQUEST RETURNED: ". $req->getResponseBody();
1466         }
1467         
1468         if (!$res->success) {
1469             return "MAIN REQUEST RETURNED: ". $res->errorMsg;
1470         }
1471          
1472         return true;
1473          
1474         
1475         
1476         
1477         
1478     } 
1479     
1480     
1481     function download($roo)
1482     {
1483         
1484          //DB_DAtaObject::debugLevel(1);
1485         
1486         $r= DB_DataObject::Factory('shipitem');
1487         $r->shiphead($this);
1488         $r->autoJoin();
1489         $r->joinAddItem();
1490         $r->joinAddCoitem();
1491         //$r->applyFilters(array(), $roo->authUser, $roo);
1492         
1493         $r->orderBy('coitem_linenumber ASC, coitem_subnumber ASC');
1494         
1495         $order = $this->order();
1496         $loc = $this->location();
1497         $shipto = $this->shipto();
1498         
1499         require_once 'Pman/Core/SimpleExcel.php';
1500         $s = new Pman_Core_SimpleExcel(
1501             $r->fetchAll(false,false,'toArray'),
1502             array(
1503                 'head' => array( 
1504                                 
1505                         array( "Ship Date:", date('Y-m-d', strtotime($this->shiphead_shipdate))),
1506                         array( "Order ID:", $order->cohead_number),
1507                         array( "From:", $loc->location_name),
1508                         array( "To:", implode("\n", $shipto->address())),
1509                 ),
1510                 'cols' => array(
1511                     
1512                     array(
1513                         'header'=> "Item Code",
1514                         'dataIndex'=> 'item_number',
1515                         'width'=>  100,
1516                     //'renderer' => array($this, 'getThumb')
1517                     ),
1518                     array(
1519                         'header'=> "Description",
1520                         'dataIndex'=> 'item_descrip1',
1521                         'width'=>  300,
1522                     //'renderer' => array($this, 'getThumb')
1523                     ),
1524                     array(
1525                         'header'=> "Quantity",
1526                         'dataIndex'=> 'shipitem_qty',
1527                         'width'=>  50,
1528                     //'renderer' => array($this, 'getThumb')
1529                     ),
1530                 ),
1531                 
1532                 'workbook' => 'Shipment'
1533             
1534              )
1535         );
1536         $s->send($this->shiphead_number.date("-d-M-Y").'.xls');
1537         
1538          
1539         
1540     }
1541     
1542     function setDelivery($roo)
1543     {
1544         if(empty($this->shiphead_shipdate) || empty($this->shiphead_shipped)){
1545             $roo->jerr('Shipment has not been confirmed');
1546         }
1547         
1548         if(empty($this->shiphead_delivery_note)){
1549             $roo->jerr('Delivery note has not been filled in');
1550         }
1551         $order = $this->order();
1552         
1553         $order->shipment = clone($this);
1554
1555         $order->sendShipment($roo);
1556         
1557     }
1558     
1559     
1560 }
1561