import php.Lib; using DateTools; typedef Detail = { id : String, to_pick : Int, position : Int, } typedef Order = { id:String ,quantity:String ,to_pick:String ,priority:Date ,delivery_date:String } enum ScheduleMode { Update; Cancel; Move; } class ScheduleParameters { var row_id : String; var rowDetails: Hash; public var mode:ScheduleMode; function new(rawData : Dynamic) { var data = php.Lib.hashOfAssociativeArray(rawData); row_id = data.get('row_id'); var raw_order : Dynamic = data.get('row_order'); mode = ScheduleMode.Move; var row_ids = php.Lib.toHaxeArray(raw_order); if (row_ids!= null) { rowDetails = new Hash(); var position = 1; for(id in row_ids) { var d : Dynamic = data.get(id); var to_pick : Int = null; if(d != null) { var o = php.Lib.objectOfAssociativeArray(d); to_pick = Std.parseInt(o.to_pick); } rowDetails.set(id, { id: id ,to_pick: to_pick ,position: position }); position+=1; } } } public function setMode(action:String) { mode = switch(action) { case "update" : ScheduleMode.Update; case "cancel" : ScheduleMode.Cancel; default: ScheduleMode.Move; }; } public function position(id: String) : Null { if(rowDetails == null) return null; return rowDetails.get(id).position; } public function toPick(order: {id:String}) : Null { var orderId = ItemScheduler.orderId(order); if(rowDetails == null) return null; return rowDetails.get(orderId).to_pick; } public function priority(order : {id: String, priority: Date}) : Int { var orderId = ItemScheduler.orderId(order); var orderPosition = position(orderId); return orderPosition != null ? orderPosition : cast(order.priority.getTime(), Int); } } class ItemScheduler { var stock_id:String; var startLocation:String; var parameters:ScheduleParameters; var qoh: Int; function new(stock_id: String, startLocation, parameters : Null) { this.stock_id = stock_id; this.startLocation = startLocation; this.parameters = parameters; qoh = untyped __call__('get_qoh_on_date', this.stock_id, startLocation); } function tableHeader() { return ["Order", "Customer", "Quantity", "Before", "After", "Loc", "From", "Required Date", "Comment", "To Pick"]; } function generateTable(): Void { var startDate = Date.fromTime(0); // Sort location by date var locations = this.locations(); locations.sort(function(a, b) { var as = a.delivery.getTime(); var bs = b.delivery.getTime(); if(as < bs) return -1; else if(as > bs) return 1; else return 0; }); // Get the start location, it should be the first one var locationIter = locations.iterator(); var location = locationIter.next(); var qoh : Int = location.quantityOnHand(stock_id, null); var left = qoh; // Get the total picked var orders = this.orders(); var total_picked = 0; for(order in orders) { total_picked += toPick(order); } left -= total_picked; if(left<0) FA.error('There is more items to pick than in stock'); formatLocation(location, "Initial", left); // We display the order ordered by priority // But insert the location when needed (meaning // when we run out of item available for(order in orders) { var quantity : Int = Std.parseInt(order.quantity); var to_pick = toPick(order); // We don't need to remove the quantity which is picked // as we have already counted it in the initial location calculation. while(0 >= left && locationIter.hasNext()) { location = locationIter.next(); var quantityForLocation : Int = location.quantityOnHand(stock_id, null) + location.quantityOnOrder(stock_id); if(quantityForLocation == null || quantityForLocation == 0 || location.delivery == null) continue; left += quantityForLocation; formatLocation(location, "Delivery", left); } if(to_pick>0) qoh -= quantity else left -= quantity; var now = Date.now(); formatOrder(order, to_pick>0 ? qoh : left, now.getTime() > location.delivery.getTime() ? now : location.delivery, now); } // display the left locations while(0 >= left && locationIter.hasNext()) { location = locationIter.next(); var quantityForLocation : Int = location.quantityOnHand(stock_id, null); if(quantityForLocation == null || quantityForLocation == 0) continue; left += quantityForLocation; formatLocation(location, "Delivery", left); } } function printRow(tds : Array, attributes : Array) { php.Lib.print(''); var position : Int = 1; for(td in tds) { php.Lib.print(''); if(td) php.Lib.print(td); php.Lib.print(''); position++; } php.Lib.print(''); } static public function orderId(order) { return 'order_'+order.id; } function formatOrder(order : Dynamic, left : Int, date : Date, now : Date) { var row_id = orderId(order); var attributes = ['id = "'+row_id+'"']; var classes = []; var to_pick = toPick(order); var before : Int = left + order.quantity- to_pick; /* We have basically 3 different cases; * - the order can be fullfilled * - the order can be partially * - not at all */ if (before < 0 ) { classes.push('soldout'); } else if(left < 0) { classes.push('partial'); } else { classes.push('full'); } /* The order can also be late if we need * to wait for a delivery to get it * or early if the item is on hold. */ var required_by : Date = FA.sql2date(order.required_date); if(required_by == null) required_by = FA.sql2date(order.expiry_date); if(to_pick > 0) { classes.push('picked'); } else if(required_by != null && required_by.getTime() < date.getTime()) { classes.push('late'); } else { var hold_until : Date = FA.sql2date(order.hold_until_date); //php.Lib.print(hold_until); //php.Lib.print(date); if(hold_until == null) hold_until = FA.sql2date(order.delivery_date); if((hold_until.getTime() - FA.preHoldOffset()) > date.getTime()) { classes.push('early'); } else { classes.push('on_time'); } } // Check if the order is expired var expiry_date : Date = FA.sql2date(order.expiry_date); if(expiry_date != null && (now.getTime() > expiry_date.getTime())) classes.push('expired'); var cells : Array = [ order.order_id , ''+order.deliver_to+'' , order.quantity // '' ,before ,left ,order.from_stk_loc ,order.hold_until_date ,order.required_date ,order.comment ,quantity_box(row_id, order, left) ]; attributes.push('class="'+classes.join(' ')+'"'); printRow(cells, attributes); } function quantity_box(row_id, order, left:Int) { var maxQuantity = order.quantity; var quantity = order.to_pick; var available = order.quantity > left ? left : order.quantity; var user_pick = parameters == null ? quantity : parameters.toPick(order); if (user_pick == null) user_pick = 0; var inputs : Array = []; if(maxQuantity > 12) { inputs.push(''+(quantity == null ? 0 : quantity)+' '); inputs.push(''); } else { inputs.push(''); for(q in 0...(maxQuantity+1)) { var last_available = untyped __php__(' $q == $available ? true: false'); var current_pick = untyped __php__(' $q == $user_pick ? true:false'); var picked = untyped __php__(' $q == $quantity'); var differs = untyped __php__('$user_pick != $quantity'); var checked = current_pick ? "checked": ""; var klass : String = q > available ? 'early' : ""; inputs.push(''); inputs.push(picked ? ''+q+'' : ''+q); inputs.push(''); if(last_available) { inputs.push(''); inputs.push(''); } } inputs.push(''); } return inputs.join(''); } function formatLocation(location : Location, type: String, left : Int) { var cells = [ type ,location.name ,location.quantityOnHand(stock_id, null) ,left-location.quantityOnHand(stock_id, null) ,left ,location.code ,location.delivery.getTime() == 0 ? '' : location.delivery.format("%F") ,"" ,"" ,"" ]; printRow(cells, ['class = "tableheader location"', 'id = "loc_'+location.code+'"']); } /* function schedules() { //return orders()+locations(); //return orders(); return cast(locations(), Array); } */ private function loadOrders() { var tb : String = untyped __php__('TB_PREF'); var sql : String = "SELECT * , d.quantity as qty, d.priority AS pp, p.quantity AS to_pick FROM "+tb+"denorm_order_details_queue d JOIN "+tb+"sales_order_details od ON (od.id = d.id) JOIN "+tb+"sales_orders so ON (so.order_no = d.order_id) LEFT JOIN ("+FA.pick_query()+") p ON (p.detail_id = od.id) WHERE stock_id = '"+this.stock_id+"' AND od.trans_type = 30 ORDER by quantity_before, d.priority"; return FA.query(sql); } function orders():Array { var rows = loadOrders(); var orderList = []; for(row in rows) { // for some reason, priority is null // so we use the pp field var a:Dynamic = php.Lib.objectOfAssociativeArray(row); var order:Order = a; order.priority = Date.fromString(a.pp); order.quantity = a.qty; orderList.push(order); }; if(parameters != null) { orderList.sort(function(a, b) { return parameters.priority(a)-parameters.priority(b); }); } for(order in orderList) { } return orderList; } function locations() { var TB = FA.tb(); var sql = 'SELECT * FROM '+TB+'locations'; var _locs = []; for(row in FA.query(sql)) { var location = new Location(row); if(location.code == startLocation) { location.delivery = Date.fromTime(0); } else if(location.delivery == null) { continue; } _locs.push(location); } return _locs; } function purcharseOrders() { var TB = FA.tb(); var sql = 'SELECT SUM(quantity_ordered - quantity_received) as quantity into_stock_location AS location FROM '+TB+'purch_order_details NATURAL JOIN '+TB+'purch_orders WHERE item_code = "'+this.stock_id+'" AND quantity_ordered > quantity_received GROUP by item_code,delivery_date, into_stock_location ORDER by delivery_date' ; return FA.query(sql); } public function needsUpdate():Bool { return parameters != null && parameters.mode == ScheduleMode.Move; } function update() { var orders = this.orders(); var priorities = Lambda.array(Lambda.map(orders, function(o) { return o.priority;})); priorities.sort(function(a,b) { var as = a.toString(); var bs = b.toString(); if (as < bs) return -1; if( as > bs) return 1; return 0; }); var iter = priorities.iterator(); var p = iter.next(); var position:Int = 0-priorities.length; for(order in orders) { var new_priority = DateTools.delta(p, 1000*position); // Update priority untyped __call__ ('update_order_detail_priority', order.id, new_priority.toString()); // Update to pick table if needed if (Std.parseInt(order.to_pick) != toPick(order)) { untyped __call__ ('update_pick', order.id, toPick(order)); } position +=1; p = iter.next(); } untyped __call__ ('update_queue_quantity_for_item', stock_id); } public function action() { if(parameters != null && parameters.mode == ScheduleMode.Update) { update(); } } public function toPick(order) { var picked : Int = Std.parseInt(order.to_pick); var user_picked : Int = null; if(parameters != null) user_picked = parameters.toPick(order); return user_picked == null ? picked : user_picked ; } }