Picked = Green + fix null quantity => 0.
[order_line_extra.git] / haxe / ItemScheduler.hx
1 import php.Lib;
2 using DateTools;
3
4 typedef Detail = {
5         id : String,
6         to_pick : Int,
7         position : Int,
8 }
9
10 typedef Order = {
11                 id:String
12                 ,quantity:String
13                 ,to_pick:String
14                 ,priority:Date
15                 ,delivery_date:String
16
17 }
18
19 enum ScheduleMode {
20         Update;
21         Cancel;
22         Move;
23 }
24
25 class ScheduleParameters {
26         var row_id : String;
27         var rowDetails: Hash<Detail>;
28         public var mode:ScheduleMode;
29
30         function new(rawData : Dynamic) {
31                 var data  = php.Lib.hashOfAssociativeArray(rawData);
32                 row_id = data.get('row_id');
33                 var raw_order : Dynamic = data.get('row_order');
34                 mode = ScheduleMode.Move;
35
36                 var row_ids = php.Lib.toHaxeArray(raw_order);
37                 if (row_ids!= null) {
38                         rowDetails = new Hash<Detail>();
39                         var position = 1;
40                         for(id in row_ids) {
41                                 var d : Dynamic = data.get(id);
42
43                                 var to_pick : Int = null;
44                                 if(d != null) {
45                                                 var o = php.Lib.objectOfAssociativeArray(d);
46                                                 to_pick = Std.parseInt(o.to_pick);
47                                 }
48                                 
49
50                                 rowDetails.set(id, {
51                                         id: id
52                                         ,to_pick: to_pick
53                                         ,position: position
54                                         });
55                                 position+=1;
56                         }
57                 }
58         }
59
60         public function setMode(action:String) {
61                 mode = switch(action) {
62                         case "update" :
63                                 ScheduleMode.Update;
64                         case "cancel" :
65                                 ScheduleMode.Cancel;
66                         default:
67                                 ScheduleMode.Move;
68                 };
69         }
70
71         public function position(id: String) : Null<Int> {
72                 if(rowDetails == null) return null;
73                 return rowDetails.get(id).position;
74
75         }
76
77         public function toPick(order: {id:String}) : Null<Int> {
78                 var orderId = ItemScheduler.orderId(order);
79                 if(rowDetails == null) return null;
80                 return rowDetails.get(orderId).to_pick;
81
82         }
83
84         public function priority(order : {id: String, priority: Date})  : Int {
85                 var orderId = ItemScheduler.orderId(order);
86                 var orderPosition = position(orderId);
87                 return orderPosition != null  ? orderPosition : cast(order.priority.getTime(), Int);
88         }
89 }
90
91
92 class ItemScheduler {
93         var stock_id:String;
94         var startLocation:String;
95         var parameters:ScheduleParameters; 
96         var qoh: Int;
97         function new(stock_id: String, startLocation, parameters : Null<ScheduleParameters>) {
98                 this.stock_id = stock_id;
99                 this.startLocation = startLocation;
100                 this.parameters = parameters;
101                 qoh =  untyped __call__('get_qoh_on_date', this.stock_id, startLocation);
102         }
103
104         function tableHeader() {
105                 return ["Order", "Customer", "Quantity", "Before", "After", "Loc", "From",  "Required Date", "Comment", "To Pick"];
106         }
107
108         function generateTable(): Void {
109                 var startDate = Date.fromTime(0);
110
111                 // Sort location by date
112                 var locations = this.locations();
113                 locations.sort(function(a, b) {
114                         var as = a.delivery.getTime();
115                         var bs = b.delivery.getTime();
116                         if(as < bs) 
117                                 return -1;
118                         else if(as > bs)
119                                 return 1;
120                         else return 0;
121                                 });
122
123                 // Get the start location, it should be the first one
124                 var locationIter = locations.iterator();
125                 var location  = locationIter.next();
126                 var qoh : Int = location.quantityOnHand(stock_id, null);
127                 var left = qoh;
128
129                 // Get the total picked
130                 var orders = this.orders();
131                 var total_picked = 0;
132                 for(order in orders) {
133                                 total_picked += toPick(order);
134                 }
135
136                 left -= total_picked;
137                 if(left<0) FA.error('There is more items to pick than in stock');
138                 formatLocation(location, "Initial",  left);
139
140                 // We display the order ordered by priority 
141                 // But insert the location when needed (meaning
142                 // when we run out of item available
143
144                 for(order in orders) {
145                         var quantity : Int = Std.parseInt(order.quantity);
146                         var to_pick = toPick(order);
147                         // We don't need to remove the quantity which is picked
148                         // as we have already counted it in the initial location calculation.
149
150                         while(0 >= left && locationIter.hasNext()) {
151                                 location = locationIter.next();
152                                 var quantityForLocation : Int = location.quantityOnHand(stock_id, null) + location.quantityOnOrder(stock_id);
153                                 if(quantityForLocation == null || quantityForLocation == 0 || location.delivery == null) continue;
154                                 left += quantityForLocation;
155                                 formatLocation(location, "Delivery", left);
156                         }
157                         if(to_pick>0)
158                                 qoh -=  quantity
159                         else
160                                 left -= quantity;
161
162                         var now = Date.now();
163                         formatOrder(order, to_pick>0 ? qoh : left, now.getTime() > location.delivery.getTime() ? now : location.delivery, now);
164
165                 }
166                 // display the left locations
167                         while(0 >= left && locationIter.hasNext()) {
168                                 location = locationIter.next();
169                                 var quantityForLocation : Int = location.quantityOnHand(stock_id, null);
170                                 if(quantityForLocation == null || quantityForLocation == 0) continue;
171                                 left += quantityForLocation;
172                                 formatLocation(location, "Delivery", left);
173                         }
174
175         }
176
177         function printRow(tds : Array<Dynamic>, attributes : Array<String>) {
178                 php.Lib.print('<tr '+attributes.join(' ')+'>');
179                 var position : Int = 1;
180                 for(td in tds) {
181                         php.Lib.print('<td class="cell_'+position+'">');
182                         if(td) php.Lib.print(td);
183                         php.Lib.print('</td>');
184                         position++;
185                 }
186                 php.Lib.print('</tr>');
187         }
188
189         static public function orderId(order) {
190                 return 'order_'+order.id;
191         }
192
193         function formatOrder(order : Dynamic, left : Int, date : Date, now : Date) {
194                 var row_id = orderId(order);
195                 var attributes = ['id = "'+row_id+'"'];
196                 var classes = [];
197                 var to_pick = toPick(order);
198                 var before : Int = left + order.quantity- to_pick;
199                 /* We have basically 3 different cases;
200                  * - the order can be fullfilled
201                  * - the order can be partially 
202                  * - not at all
203                  */
204                 if (before < 0 ) {
205                         classes.push('soldout');
206                 }
207                 else if(left < 0) {
208                         classes.push('partial');
209                 }
210                 else {
211                         classes.push('full');
212                 }
213
214                 /* The order can also be late if we need
215                  * to wait for a delivery to get it
216                  * or early if the item is on hold.
217                  */
218                 var required_by : Date = FA.sql2date(order.required_date);
219                 if(required_by == null) required_by = FA.sql2date(order.expiry_date);
220                 if(to_pick > 0) {
221                         classes.push('picked');
222                 }
223                 else    if(required_by != null && required_by.getTime() < date.getTime()) {
224                         classes.push('late');
225                 }
226                 else {
227                         var hold_until : Date = FA.sql2date(order.hold_until_date);
228                         //php.Lib.print(hold_until);
229                         //php.Lib.print(date);
230                         if(hold_until == null) hold_until = FA.sql2date(order.delivery_date);
231                         if((hold_until.getTime() - FA.preHoldOffset())  > date.getTime()) {
232                                 classes.push('early');
233                         }
234                         else {
235                                 classes.push('on_time');
236                         }
237                 }
238
239                 // Check if the order is expired
240                 var expiry_date : Date = FA.sql2date(order.expiry_date);
241                 if(expiry_date != null && (now.getTime() > expiry_date.getTime())) classes.push('expired');
242
243                 var cells : Array <Dynamic> = [
244                         order.order_id 
245                         , '<a href="/modules/order_line_extra/order_lines_view.php?customer_id='+Std.string(order.debtor_no)+'">'+order.deliver_to+'</a>'
246                         , order.quantity // '<input type="text" name="'+row_id+'[quantity]" value="'+order.quantity+'">'
247                         ,before
248                         ,left
249                         ,order.from_stk_loc
250                         ,order.hold_until_date
251                         ,order.required_date 
252                         ,order.comment
253                         ,quantity_box(row_id, order, left)
254                         ];
255
256                 attributes.push('class="'+classes.join(' ')+'"');
257                 printRow(cells, attributes);
258
259         }
260
261         function quantity_box(row_id, order, left:Int) {
262                 var maxQuantity =  order.quantity;
263                 var quantity = order.to_pick;
264                 var available = order.quantity > left ? left : order.quantity;
265                 var user_pick = parameters == null ? quantity : parameters.toPick(order);
266                 if (user_pick == null) user_pick = 0;
267                         var inputs : Array<String> = [];
268                 if(maxQuantity > 12) {
269                         inputs.push('<span class="picked">'+(quantity == null ? 0 : quantity)+' </span>');
270                         inputs.push('<input type="text" name="'+row_id+'[to_pick]" value="'+user_pick+'" onchange=\'onPick(this);\'>');
271                 }
272                 else  {
273                         inputs.push('<span class="pickable">');
274                         for(q in 0...(maxQuantity+1)) {
275                                 var last_available = untyped __php__(' $q == $available ? true: false'); 
276                                 var current_pick = untyped __php__(' $q == $user_pick ? true:false');
277                                 var picked = untyped __php__(' $q == $quantity');
278                                 var differs = untyped __php__('$user_pick != $quantity');
279                                 var checked = current_pick ? "checked": ""; 
280                                 var klass : String = q > available ? 'early' : "";
281                                 inputs.push('<input type="radio" name="'+row_id+'[to_pick]" value="'+q+'" '+checked+' class="'+klass+'"
282                                 onclick=\'onPick(this);\'
283                                 >');
284                                 inputs.push(picked ? '<span class="picked">'+q+'</span>' : ''+q);
285                                 inputs.push('</input>');
286                                 if(last_available) {
287                         inputs.push('</span>');
288                         inputs.push('<span class="partial">');
289                                 }
290                         }
291                         inputs.push('</span>');
292
293                 }
294                 return inputs.join(''); 
295         }
296
297         function formatLocation(location : Location, type: String,  left : Int) {
298                 var cells = [
299                         type
300                         ,location.name
301                         ,location.quantityOnHand(stock_id, null)
302                         ,left-location.quantityOnHand(stock_id, null)
303                         ,left
304                         ,location.code
305                         ,location.delivery.getTime() == 0 ? '' : location.delivery.format("%F")
306                         ,""
307                         ,""
308                         ,""
309                         ];
310
311                 printRow(cells, ['class = "tableheader location"', 'id = "loc_'+location.code+'"']);
312         }
313
314         /*
315                  function schedules() {
316 //return orders()+locations();
317 //return  orders();
318 return cast(locations(), Array<Dynamic>);
319
320 }
321          */
322
323 private function loadOrders() {
324         var tb : String =  untyped __php__('TB_PREF');
325         var sql : String = "SELECT *  , d.quantity as qty,   d.priority AS pp, p.quantity AS to_pick
326                 FROM "+tb+"denorm_order_details_queue  d
327                 JOIN "+tb+"sales_order_details od ON (od.id = d.id)
328                 JOIN "+tb+"sales_orders so ON (so.order_no = d.order_id)
329                 LEFT JOIN ("+FA.pick_query()+") p ON (p.detail_id = od.id)
330                 WHERE stock_id = '"+this.stock_id+"'
331                 AND od.trans_type = 30
332                 ORDER by quantity_before, d.priority";
333
334         return FA.query(sql);
335 }
336
337 function orders():Array<Order>  {
338         var rows = loadOrders();
339         var orderList = [];
340         for(row in rows) {
341                 // for some reason, priority is null
342                 // so we use the pp field
343                 var a:Dynamic = php.Lib.objectOfAssociativeArray(row);
344                 var order:Order = a;
345                 order.priority = Date.fromString(a.pp);
346                 order.quantity = a.qty;
347                 orderList.push(order);
348         };
349
350         if(parameters != null) {
351                 orderList.sort(function(a, b) { return parameters.priority(a)-parameters.priority(b); });
352
353         }
354         for(order in orderList) {
355         }
356
357         return orderList;
358 }
359
360
361
362 function locations() {
363         var TB = FA.tb();
364         var sql = 'SELECT * 
365                 FROM '+TB+'locations';
366         var _locs = [];
367         for(row in FA.query(sql)) {
368                 var location = new Location(row);
369                 if(location.code == startLocation) {
370                         location.delivery =  Date.fromTime(0);
371                 }
372                 else if(location.delivery == null) {
373                         continue;
374                 }
375                 _locs.push(location);
376         }
377
378         return _locs;
379
380 }
381
382
383 function purcharseOrders()  {
384         var TB = FA.tb();
385         var sql = 'SELECT SUM(quantity_ordered - quantity_received) as quantity
386                 into_stock_location AS location
387                 FROM '+TB+'purch_order_details
388                 NATURAL JOIN '+TB+'purch_orders
389                 WHERE item_code = "'+this.stock_id+'"
390                 AND quantity_ordered > quantity_received
391                 GROUP by item_code,delivery_date, into_stock_location
392                 ORDER by delivery_date' ;
393
394         return  FA.query(sql);
395 }
396
397 public function needsUpdate():Bool {
398         return parameters != null && parameters.mode == ScheduleMode.Move;
399 }
400
401 function update() {
402         var orders = this.orders();
403         var priorities = Lambda.array(Lambda.map(orders, function(o) { return o.priority;}));
404         priorities.sort(function(a,b) {
405                         var as = a.toString();
406                         var bs = b.toString();
407                         if (as < bs) return -1;
408                         if( as > bs) return 1;
409                         return 0;
410                         });
411
412         var iter = priorities.iterator();
413         var p = iter.next();
414         var position:Int = 0-priorities.length;
415         for(order in orders) {
416                 var new_priority = DateTools.delta(p, 1000*position);
417                 // Update priority
418                 untyped __call__ ('update_order_detail_priority', order.id, new_priority.toString());
419
420                 // Update to pick table if needed
421                 if (Std.parseInt(order.to_pick) != toPick(order)) {
422                         untyped __call__ ('update_pick', order.id, toPick(order));
423                 }
424                 
425
426                 position +=1;
427                 p = iter.next();
428         }
429         untyped __call__ ('update_queue_quantity_for_item', stock_id);
430
431 }
432
433 public function action() {
434         if(parameters != null && parameters.mode == ScheduleMode.Update) {
435                 update();
436         }
437 }
438
439 public function toPick(order) {
440         var picked : Int = Std.parseInt(order.to_pick);
441         var user_picked : Int = null;
442         if(parameters != null) user_picked = parameters.toPick(order);
443         return user_picked == null  ? picked : user_picked ;
444 }
445
446
447 }
448
449