operation.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/model.h"
23 
24 namespace frepple
25 {
26 
27 template<class Operation> DECLARE_EXPORT Tree utils::HasName<Operation>::st;
34 DECLARE_EXPORT Operation::Operationlist Operation::nosubOperations;
36 
37 
39 {
40  // Initialize the metadata
41  metadata = new MetaCategory("operation", "operations", reader, writer);
42 
43  // Initialize the Python class
45 }
46 
47 
49 {
50  // Initialize the metadata
51  metadata = new MetaClass("operation", "operation_fixed_time",
52  Object::createString<OperationFixedTime>, true);
53 
54  // Initialize the Python class
56 }
57 
58 
60 {
61  // Initialize the metadata
62  metadata = new MetaClass("operation", "operation_time_per",
63  Object::createString<OperationTimePer>);
64 
65  // Initialize the Python class
67 }
68 
69 
71 {
72  // Initialize the metadata
73  metadata = new MetaClass("operation", "operation_alternate",
74  Object::createString<OperationAlternate>);
75 
76  // Initialize the Python class
77  FreppleClass<OperationAlternate,Operation>::getType().addMethod("addAlternate", OperationAlternate::addAlternate, METH_KEYWORDS, "add an alternate");
79 }
80 
81 
83 {
84  // Initialize the metadata
85  metadata = new MetaClass("operation", "operation_routing",
86  Object::createString<OperationRouting>);
87 
88  // Initialize the Python class
89  FreppleClass<OperationRouting,Operation>::getType().addMethod("addStep", OperationRouting::addStep, METH_VARARGS , "add steps to the routing");
91 }
92 
93 
95 {
96  // Initialize the metadata.
97  // There is NO factory method
98  metadata = new MetaClass("operation", "operation_setup");
99 
100  // Initialize the Python class
102 
103  // Create a generic setup operation.
104  // This will be the only instance of this class.
105  setupoperation = add(new OperationSetup("setup operation"));
106 
107  return tmp;
108 }
109 
110 
112 {
113  // Delete all existing operationplans (even locked ones)
114  deleteOperationPlans(true);
115 
116  // The Flow and Load objects are automatically deleted by the destructor
117  // of the Association list class.
118 
119  // Remove the reference to this operation from all items
120  for (Item::iterator k = Item::begin(); k != Item::end(); ++k)
121  if (k->getOperation() == this) k->setOperation(NULL);
122 
123  // Remove the reference to this operation from all demands
124  for (Demand::iterator l = Demand::begin(); l != Demand::end(); ++l)
125  if (l->getOperation() == this) l->setOperation(NULL);
126 
127  // Remove the reference to this operation from all buffers
128  for (Buffer::iterator m = Buffer::begin(); m != Buffer::end(); ++m)
129  if (m->getProducingOperation() == this) m->setProducingOperation(NULL);
130 
131  // Remove the operation from its super-operations and sub-operations
132  // Note that we are not using a for-loop since our function is actually
133  // updating the list of super-operations at the same time as we move
134  // through it.
135  while (!getSuperOperations().empty())
137 }
138 
139 
141 {
142  // Note that we are not using a for-loop since our function is actually
143  // updating the list of super-operations at the same time as we move
144  // through it.
145  while (!getSubOperations().empty())
147 }
148 
149 
151 {
152  // Note that we are not using a for-loop since our function is actually
153  // updating the list of super-operations at the same time as we move
154  // through it.
155  while (!getSubOperations().empty())
157 }
158 
159 
161  Demand* l, OperationPlan* ow, unsigned long i,
162  bool makeflowsloads) const
163 {
164  OperationPlan *opplan = new OperationPlan();
165  initOperationPlan(opplan,q,s,e,l,ow,i,makeflowsloads);
166  return opplan;
167 }
168 
169 
171 (Date thedate, TimePeriod duration, bool forward,
172  TimePeriod *actualduration) const
173 {
174  int calcount = 0;
175  // Initial size of 10 should do for 99.99% of all cases
176  vector<Calendar::EventIterator*> cals(10);
177 
178  // Default actual duration
179  if (actualduration) *actualduration = duration;
180 
181  try
182  {
183  // Step 1: Create an iterator on each of the calendars
184  // a) operation's location
185  if (loc && loc->getAvailable())
186  cals[calcount++] = new Calendar::EventIterator(loc->getAvailable(), thedate, forward);
187  /* @todo multiple availability calendars are not implemented yet
188  for (Operation::loadlist::const_iterator g=loaddata.begin();
189  g!=loaddata.end(); ++g)
190  {
191  Resource* res = g->getResource();
192  if (res->getMaximum())
193  // b) resource size calendar
194  cals[calcount++] = new Calendar::EventIterator(
195  res->getMaximum(),
196  thedate
197  );
198  if (res->getLocation() && res->getLocation()->getAvailable())
199  // c) resource location
200  cals[calcount++] = new Calendar::EventIterator(
201  res->getLocation()->getAvailable(),
202  thedate
203  );
204  }
205  */
206 
207  // Special case: no calendars at all
208  if (calcount == 0)
209  return forward ?
210  DateRange(thedate, thedate+duration) :
211  DateRange(thedate-duration, thedate);
212 
213  // Step 2: Iterate over the calendar dates to find periods where all
214  // calendars are simultaneously effective.
215  DateRange result;
216  Date curdate = thedate;
217  bool status = false;
218  TimePeriod curduration = duration;
219  while (true)
220  {
221  // Check whether all calendars are available
222  bool available = true;
223  for (int c = 0; c < calcount && available; c++)
224  {
225  const Calendar::Bucket *tmp = cals[c]->getBucket();
226  if (tmp)
227  available = tmp->getBool();
228  else
229  available = cals[c]->getCalendar()->getBool();
230  }
231  curdate = cals[0]->getDate();
232 
233  if (available && !status)
234  {
235  // Becoming available after unavailable period
236  thedate = curdate;
237  status = true;
238  if (forward && result.getStart() == Date::infinitePast)
239  // First available time - make operation start at this time
240  result.setStart(curdate);
241  else if (!forward && result.getEnd() == Date::infiniteFuture)
242  // First available time - make operation end at this time
243  result.setEnd(curdate);
244  }
245  else if (!available && status)
246  {
247  // Becoming unavailable after available period
248  status = false;
249  if (forward)
250  {
251  // Forward
252  TimePeriod delta = curdate - thedate;
253  if (delta >= curduration)
254  {
255  result.setEnd(thedate + curduration);
256  break;
257  }
258  else
259  curduration -= delta;
260  }
261  else
262  {
263  // Backward
264  TimePeriod delta = thedate - curdate;
265  if (delta >= curduration)
266  {
267  result.setStart(thedate - curduration);
268  break;
269  }
270  else
271  curduration -= delta;
272  }
273  }
274  else if (forward && curdate == Date::infiniteFuture)
275  {
276  // End of forward iteration
277  if (available)
278  {
279  TimePeriod delta = curdate - thedate;
280  if (delta >= curduration)
281  result.setEnd(thedate + curduration);
282  else if (actualduration)
283  *actualduration = duration - curduration;
284  }
285  else if (actualduration)
286  *actualduration = duration - curduration;
287  break;
288  }
289  else if (!forward && curdate == Date::infinitePast)
290  {
291  // End of backward iteration
292  if (available)
293  {
294  TimePeriod delta = thedate - curdate;
295  if (delta >= curduration)
296  result.setStart(thedate - curduration);
297  else if (actualduration)
298  *actualduration = duration - curduration;
299  }
300  else if (actualduration)
301  *actualduration = duration - curduration;
302  break;
303  }
304 
305  // Advance to the next event
306  if (forward) ++(*cals[0]);
307  else --(*cals[0]);
308  }
309 
310  // Step 3: Clean up
311  while (calcount) delete cals[--calcount];
312  return result;
313  }
314  catch (...)
315  {
316  // Clean up
317  while (calcount) delete cals[calcount--];
318  // Rethrow the exception
319  throw;
320  }
321 }
322 
323 
325 (Date start, Date end, TimePeriod *actualduration) const
326 {
327  // Switch start and end if required
328  if (end < start)
329  {
330  Date tmp = start;
331  start = end;
332  end = tmp;
333  }
334 
335  int calcount = 0;
336  // Initial size of 10 should do for 99.99% of all cases
337  vector<Calendar::EventIterator*> cals(10);
338 
339  // Default actual duration
340  if (actualduration) *actualduration = 0L;
341 
342  try
343  {
344  // Step 1: Create an iterator on each of the calendars
345  // a) operation's location
346  if (loc && loc->getAvailable())
347  cals[calcount++] = new Calendar::EventIterator(loc->getAvailable(), start);
348  /* @todo multiple availability calendars are not implmented yet
349  for (Operation::loadlist::const_iterator g=loaddata.begin();
350  g!=loaddata.end(); ++g)
351  {
352  Resource* res = g->getResource();
353  if (res->getMaximum())
354  // b) resource size calendar
355  cals[calcount++] = new Calendar::EventIterator(
356  res->getMaximum(),
357  start
358  );
359  if (res->getLocation() && res->getLocation()->getAvailable())
360  // c) resource location
361  cals[calcount++] = new Calendar::EventIterator(
362  res->getLocation()->getAvailable(),
363  start
364  );
365  }
366  */
367 
368  // Special case: no calendars at all
369  if (calcount == 0)
370  {
371  if (actualduration) *actualduration = end - start;
372  return DateRange(start, end);
373  }
374 
375  // Step 2: Iterate over the calendar dates to find periods where all
376  // calendars are simultaneously effective.
377  DateRange result;
378  Date curdate = start;
379  bool status = false;
380  while (true)
381  {
382  // Check whether all calendar are available
383  bool available = true;
384  for (int c = 0; c < calcount && available; c++)
385  {
386  if (cals[c]->getBucket())
387  available = cals[c]->getBucket()->getBool();
388  else
389  available = cals[c]->getCalendar()->getBool();
390  }
391  curdate = cals[0]->getDate();
392 
393  if (available && !status)
394  {
395  // Becoming available after unavailable period
396  if (curdate >= end)
397  {
398  // Leaving the desired date range
399  result.setEnd(start);
400  break;
401  }
402  start = curdate;
403  status = true;
404  if (result.getStart() == Date::infinitePast)
405  // First available time - make operation start at this time
406  result.setStart(curdate);
407  }
408  else if (!available && status)
409  {
410  // Becoming unavailable after available period
411  if (curdate >= end)
412  {
413  // Leaving the desired date range
414  if (actualduration) *actualduration += end - start;
415  result.setEnd(end);
416  break;
417  }
418  status = false;
419  if (actualduration) *actualduration += curdate - start;
420  start = curdate;
421  }
422  else if (curdate >= end)
423  {
424  // Leaving the desired date range
425  if (available)
426  {
427  if (actualduration) *actualduration += end - start;
428  result.setEnd(end);
429  break;
430  }
431  else
432  result.setEnd(start);
433  break;
434  }
435 
436  // Advance to the next event
437  ++(*cals[0]);
438  }
439 
440  // Step 3: Clean up
441  while (calcount) delete cals[--calcount];
442  return result;
443  }
444  catch (...)
445  {
446  // Clean up
447  while (calcount) delete cals[calcount--];
448  // Rethrow the exception
449  throw;
450  }
451 }
452 
453 
455  double q, const Date& s, const Date& e, Demand* l, OperationPlan* ow,
456  unsigned long i, bool makeflowsloads) const
457 {
458  opplan->oper = const_cast<Operation*>(this);
459  opplan->setDemand(l);
460  opplan->id = i;
461 
462  // Setting the owner first. Note that the order is important here!
463  // For alternates & routings the quantity needs to be set through the owner.
464  opplan->setOwner(ow);
465 
466  // Setting the dates and quantity
467  setOperationPlanParameters(opplan,q,s,e);
468 
469  // Create the loadplans and flowplans, if allowed
470  if (makeflowsloads) opplan->createFlowLoads();
471 
472  // Update flow and loadplans, and mark for problem detection
473  opplan->update();
474 }
475 
476 
477 DECLARE_EXPORT void Operation::deleteOperationPlans(bool deleteLockedOpplans)
478 {
479  OperationPlan::deleteOperationPlans(this, deleteLockedOpplans);
480 }
481 
482 
484 {
485  // Note that this class is abstract and never instantiated directly. There is
486  // therefore no reason to ever write a header.
487  assert(m == NOHEADER);
488 
489  // Write the fields
491  Plannable::writeElement(o, tag);
492  if (post_time)
493  o->writeElement(Tags::tag_posttime, post_time);
494  if (pre_time)
495  o->writeElement(Tags::tag_pretime, pre_time);
496  if (getCost() != 0.0)
498  if (fence)
499  o->writeElement(Tags::tag_fence, fence);
500  if (size_minimum != 1.0)
501  o->writeElement(Tags::tag_size_minimum, size_minimum);
502  if (size_multiple > 0.0)
503  o->writeElement(Tags::tag_size_multiple, size_multiple);
504  if (size_maximum < DBL_MAX)
505  o->writeElement(Tags::tag_size_maximum, size_maximum);
506  if (loc)
508 
509  // Write extra plan information
510  if ((o->getContentType() == XMLOutput::PLAN
511  || o->getContentType() == XMLOutput::PLANDETAIL) && first_opplan)
512  {
514  for (OperationPlan::iterator i(this); i!=OperationPlan::end(); ++i)
517  }
518 }
519 
520 
522 {
523  if (pAttr.isA(Tags::tag_flow)
524  && pIn.getParentElement().first.isA(Tags::tag_flows))
525  {
526  Flow *f =
527  dynamic_cast<Flow*>(MetaCategory::ControllerDefault(Flow::metadata,pIn.getAttributes()));
528  if (f) f->setOperation(this);
529  pIn.readto(f);
530  }
531  else if (pAttr.isA (Tags::tag_load)
532  && pIn.getParentElement().first.isA(Tags::tag_loads))
533  {
534  Load* l = new Load();
535  l->setOperation(this);
536  pIn.readto(&*l);
537  }
538  else if (pAttr.isA (Tags::tag_operationplan))
540  else if (pAttr.isA (Tags::tag_location))
542 }
543 
544 
545 DECLARE_EXPORT void Operation::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
546 {
547  if (pAttr.isA (Tags::tag_fence))
548  setFence(pElement.getTimeperiod());
549  else if (pAttr.isA (Tags::tag_size_minimum))
550  setSizeMinimum(pElement.getDouble());
551  else if (pAttr.isA (Tags::tag_cost))
552  setCost(pElement.getDouble());
553  else if (pAttr.isA (Tags::tag_size_multiple))
554  setSizeMultiple(pElement.getDouble());
555  else if (pAttr.isA (Tags::tag_size_maximum))
556  setSizeMaximum(pElement.getDouble());
557  else if (pAttr.isA (Tags::tag_pretime))
558  setPreTime(pElement.getTimeperiod());
559  else if (pAttr.isA (Tags::tag_posttime))
560  setPostTime(pElement.getTimeperiod());
561  else if (pAttr.isA (Tags::tag_location))
562  {
563  Location *l = dynamic_cast<Location*>(pIn.getPreviousObject());
564  if (l) setLocation(l);
565  else throw LogicException("Incorrect object type during read operation");
566  }
567  else
568  {
569  Plannable::endElement(pIn, pAttr, pElement);
570  HasDescription::endElement(pIn, pAttr, pElement);
571  }
572 }
573 
574 
577 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
578 {
579  // Invalid call to the function, or locked operationplan.
580  if (!opplan || q<0)
581  throw LogicException("Incorrect parameters for fixedtime operationplan");
582  if (opplan->getLocked())
583  return OperationPlanState(opplan);
584 
585  // All quantities are valid, as long as they are above the minimum size and
586  // below the maximum size
587  if (q > 0 && q < getSizeMinimum()) q = getSizeMinimum();
588  if (q > getSizeMaximum()) q = getSizeMaximum();
589  if (fabs(q - opplan->getQuantity()) > ROUNDING_ERROR)
590  q = opplan->setQuantity(q, false, false, execute);
591 
592  // Set the start and end date.
593  DateRange x;
594  TimePeriod actualduration;
595  if (e && s)
596  {
597  if (preferEnd) x = calculateOperationTime(e, duration, false, &actualduration);
598  else x = calculateOperationTime(s, duration, true, &actualduration);
599  }
600  else if (s) x = calculateOperationTime(s, duration, true, &actualduration);
601  else x = calculateOperationTime(e, duration, false, &actualduration);
602  if (!execute)
603  // Simulation only
604  return OperationPlanState(x, actualduration == duration ? q : 0);
605  else if (actualduration == duration)
606  // Update succeeded
607  opplan->setStartAndEnd(x.getStart(), x.getEnd());
608  else
609  // Update failed - Not enough available time
610  opplan->setQuantity(0);
611 
612  // Return value
613  return OperationPlanState(opplan);
614 }
615 
616 
618 {
619  // See if we can consolidate this operationplan with an existing one.
620  // Merging is possible only when all the following conditions are met:
621  // - id of the new opplan is not set
622  // - id of the old opplan is set
623  // - it is a fixedtime operation
624  // - it doesn't load any resources
625  // - both operationplans aren't locked
626  // - both operationplans have no owner
627  // - start and end date of both operationplans are the same
628  // - demand of both operationplans are the same
629  // - maximum operation size is not exceeded
630  // - alternate flowplans need to be on the same alternate
631  if (!o->getIdentifier() && !o->getLocked() && !o->getOwner() && getLoads().empty())
632  {
633  // Loop through candidates
634  OperationPlan::iterator x(this);
635  OperationPlan *y = NULL;
636  for (; x != OperationPlan::end() && *x < *o; ++x)
637  y = &*x;
638  if (y && y->getDates() == o->getDates() && !y->getOwner()
639  && y->getDemand() == o->getDemand() && !y->getLocked() && y->getIdentifier()
640  && y->getQuantity() + o->getQuantity() < getSizeMaximum())
641  {
642  // Check that the flowplans are on identical alternates
643  OperationPlan::FlowPlanIterator fp1 = o->beginFlowPlans();
645  while (fp1 != o->endFlowPlans())
646  {
647  if (fp1->getBuffer() != fp2->getBuffer())
648  // Different alternates - no merge possible
649  return true;
650  ++fp1;
651  ++fp2;
652  }
653  // Merging with the 'next' operationplan
654  y->setQuantity(y->getQuantity() + o->getQuantity());
655  return false;
656  }
657  if (x!= OperationPlan::end() && x->getDates() == o->getDates() && !x->getOwner()
658  && x->getDemand() == o->getDemand() && !x->getLocked() && x->getIdentifier()
659  && x->getQuantity() + o->getQuantity() < getSizeMaximum())
660  {
661  // Check that the flowplans are on identical alternates
662  OperationPlan::FlowPlanIterator fp1 = o->beginFlowPlans();
664  while (fp1 != o->endFlowPlans())
665  {
666  if (fp1->getBuffer() != fp2->getBuffer())
667  // Different alternates - no merge possible
668  return true;
669  ++fp1;
670  ++fp2;
671  }
672  // Merging with the 'previous' operationplan
673  x->setQuantity(x->getQuantity() + o->getQuantity());
674  return false;
675  }
676  }
677  return true;
678 }
679 
680 
682 (XMLOutput *o, const Keyword& tag, mode m) const
683 {
684  // Writing a reference
685  if (m == REFERENCE)
686  {
687  o->writeElement
688  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
689  return;
690  }
691 
692  // Write the complete object
693  if (m != NOHEADER) o->BeginObject
694  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
695 
696  // Write the fields
698  if (duration) o->writeElement (Tags::tag_duration, duration);
699  o->EndObject (tag);
700 }
701 
702 
704 {
705  if (pAttr.isA (Tags::tag_duration))
706  setDuration (pElement.getTimeperiod());
707  else
708  Operation::endElement (pIn, pAttr, pElement);
709 }
710 
711 
714 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
715 {
716  // Invalid call to the function.
717  if (!opplan || q<0)
718  throw LogicException("Incorrect parameters for timeper operationplan");
719  if (opplan->getLocked())
720  return OperationPlanState(opplan);
721 
722  // Respect minimum and maximum size
723  if (q > 0 && q < getSizeMinimum()) q = getSizeMinimum();
724  if (q > getSizeMaximum()) q = getSizeMaximum();
725 
726  // The logic depends on which dates are being passed along
727  DateRange x;
728  TimePeriod actual;
729  if (s && e)
730  {
731  // Case 1: Both the start and end date are specified: Compute the quantity.
732  // Calculate the available time between those dates
733  x = calculateOperationTime(s,e,&actual);
734  if (actual < duration)
735  {
736  // Start and end aren't far enough from each other to fit the constant
737  // part of the operation duration. This is infeasible.
738  if (!execute) return OperationPlanState(x,0);
739  opplan->setQuantity(0,true,false,execute);
740  opplan->setEnd(e);
741  }
742  else
743  {
744  // Calculate the quantity, respecting minimum, maximum and multiple size.
745  if (duration_per)
746  {
747  if (q * duration_per < static_cast<double>(actual - duration) + 1)
748  // Provided quantity is acceptable.
749  // Note that we allow a margin of 1 second to accept.
750  q = opplan->setQuantity(q, true, false, execute);
751  else
752  // Calculate the maximum operationplan that will fit in the window
753  q = opplan->setQuantity(
754  static_cast<double>(actual - duration) / duration_per,
755  true, false, execute);
756  }
757  else
758  // No duration_per field given, so any quantity will go
759  q = opplan->setQuantity(q, true, false, execute);
760 
761  // Updates the dates
762  TimePeriod wanted(
763  duration + static_cast<long>(duration_per * q)
764  );
765  if (preferEnd) x = calculateOperationTime(e, wanted, false, &actual);
766  else x = calculateOperationTime(s, wanted, true, &actual);
767  if (!execute) return OperationPlanState(x,q);
768  opplan->setStartAndEnd(x.getStart(),x.getEnd());
769  }
770  }
771  else if (e || !s)
772  {
773  // Case 2: Only an end date is specified. Respect the quantity and
774  // compute the start date
775  // Case 4: No date was given at all. Respect the quantity and the
776  // existing end date of the operationplan.
777  q = opplan->setQuantity(q,true,false,execute); // Round and size the quantity
778  TimePeriod wanted(duration + static_cast<long>(duration_per * q));
779  x = calculateOperationTime(e, wanted, false, &actual);
780  if (actual == wanted)
781  {
782  // Size is as desired
783  if (!execute) return OperationPlanState(x, q);
784  opplan->setStartAndEnd(x.getStart(),x.getEnd());
785  }
786  else if (actual < duration)
787  {
788  // Not feasible
789  if (!execute) return OperationPlanState(x, 0);
790  opplan->setQuantity(0,true,false);
791  opplan->setStartAndEnd(e,e);
792  }
793  else
794  {
795  // Resize the quantity to be feasible
796  double max_q = duration_per ?
797  static_cast<double>(actual-duration) / duration_per :
798  q;
799  q = opplan->setQuantity(q < max_q ? q : max_q, true, false, execute);
800  wanted = duration + static_cast<long>(duration_per * q);
801  x = calculateOperationTime(e, wanted, false, &actual);
802  if (!execute) return OperationPlanState(x, q);
803  opplan->setStartAndEnd(x.getStart(),x.getEnd());
804  }
805  }
806  else
807  {
808  // Case 3: Only a start date is specified. Respect the quantity and
809  // compute the end date
810  q = opplan->setQuantity(q,true,false,execute); // Round and size the quantity
811  TimePeriod wanted(
812  duration + static_cast<long>(duration_per * q)
813  );
814  TimePeriod actual;
815  x = calculateOperationTime(s, wanted, true, &actual);
816  if (actual == wanted)
817  {
818  // Size is as desired
819  if (!execute) return OperationPlanState(x, q);
820  opplan->setStartAndEnd(x.getStart(),x.getEnd());
821  }
822  else if (actual < duration)
823  {
824  // Not feasible
825  if (!execute) return OperationPlanState(x, 0);
826  opplan->setQuantity(0,true,false);
827  opplan->setStartAndEnd(s,s);
828  }
829  else
830  {
831  // Resize the quantity to be feasible
832  double max_q = duration_per ?
833  static_cast<double>(actual-duration) / duration_per :
834  q;
835  q = opplan->setQuantity(q < max_q ? q : max_q, true, false, execute);
836  wanted = duration + static_cast<long>(duration_per * q);
837  x = calculateOperationTime(e, wanted, false, &actual);
838  if (!execute) return OperationPlanState(x, q);
839  opplan->setStartAndEnd(x.getStart(),x.getEnd());
840  }
841  }
842 
843  // Return value
844  return OperationPlanState(opplan);
845 }
846 
847 
849 (XMLOutput *o, const Keyword& tag, mode m) const
850 {
851  // Writing a reference
852  if (m == REFERENCE)
853  {
854  o->writeElement
855  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
856  return;
857  }
858 
859  // Write the complete object
860  if (m != NOHEADER) o->BeginObject
861  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
862 
863  // Write the complete object
865  o->writeElement(Tags::tag_duration, duration);
866  o->writeElement(Tags::tag_duration_per, duration_per);
867  o->EndObject(tag);
868 }
869 
870 
872 {
873  if (pAttr.isA (Tags::tag_duration))
874  setDuration (pElement.getTimeperiod());
875  else if (pAttr.isA (Tags::tag_duration_per))
876  setDurationPer (pElement.getTimeperiod());
877  else
878  Operation::endElement (pIn, pAttr, pElement);
879 }
880 
881 
883 (XMLOutput *o, const Keyword& tag, mode m) const
884 {
885  // Writing a reference
886  if (m == REFERENCE)
887  {
888  o->writeElement
889  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
890  return;
891  }
892 
893  // Write the complete object
894  if (m != NOHEADER) o->BeginObject
895  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
896 
897  // Write the fields
899  if (steps.size())
900  {
902  for (Operationlist::const_iterator i = steps.begin(); i!=steps.end(); ++i)
905  }
906  o->EndObject(tag);
907 }
908 
909 
911 {
912  if (pAttr.isA (Tags::tag_operation))
914  else
915  Operation::beginElement(pIn, pAttr);
916 }
917 
918 
920 {
921  if (pAttr.isA (Tags::tag_operation)
922  && pIn.getParentElement().first.isA(Tags::tag_steps))
923  {
924  Operation *oper = dynamic_cast<Operation*>(pIn.getPreviousObject());
925  if (oper) addStepBack (oper);
926  else throw LogicException("Incorrect object type during read operation");
927  }
928  Operation::endElement (pIn, pAttr, pElement);
929 }
930 
931 
933 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
934 {
935  // Invalid call to the function
936  if (!opplan || q<0)
937  throw LogicException("Incorrect parameters for routing operationplan");
938  if (opplan->getLocked())
939  return OperationPlanState(opplan);
940 
941  if (!opplan->lastsubopplan || opplan->lastsubopplan->getOperation() == OperationSetup::setupoperation) // @todo replace with proper iterator
942  {
943  // No step operationplans to work with. Just apply the requested quantity
944  // and dates.
945  q = opplan->setQuantity(q,false,false,execute);
946  if (!s && e) s = e;
947  if (s && !e) e = s;
948  if (!execute) return OperationPlanState(s, e, q);
949  opplan->setStartAndEnd(s,e);
950  return OperationPlanState(opplan);
951  }
952 
953  // Behavior depends on the dates being passed.
954  // Move all sub-operationplans in an orderly fashion, either starting from
955  // the specified end date or the specified start date.
957  Date y;
958  bool realfirst = true;
959  if (e)
960  {
961  // Case 1: an end date is specified
962  for (OperationPlan* i = opplan->lastsubopplan; i; i = i->prevsubopplan)
963  {
964  if (i->getOperation() == OperationSetup::setupoperation) continue;
965  x = i->getOperation()->setOperationPlanParameters(i,q,Date::infinitePast,e,preferEnd,execute);
966  e = x.start;
967  if (realfirst)
968  {
969  y = x.end;
970  realfirst = false;
971  }
972  }
973  return OperationPlanState(x.start, y, x.quantity);
974  }
975  else if (s)
976  {
977  // Case 2: a start date is specified
978  for (OperationPlan *i = opplan->firstsubopplan; i; i = i->nextsubopplan)
979  {
980  if (i->getOperation() == OperationSetup::setupoperation) continue;
981  x = i->getOperation()->setOperationPlanParameters(i,q,s,Date::infinitePast,preferEnd,execute);
982  s = x.end;
983  if (realfirst)
984  {
985  y = x.start;
986  realfirst = false;
987  }
988  }
989  return OperationPlanState(y, x.end, x.quantity);
990  }
991  else
992  throw LogicException(
993  "Updating a routing operationplan without start or end date argument"
994  );
995 }
996 
997 
999 {
1000  // Create step suboperationplans if they don't exist yet.
1001  if (!o->lastsubopplan || o->lastsubopplan->getOperation() == OperationSetup::setupoperation)
1002  {
1003  Date d = o->getDates().getEnd();
1004  OperationPlan *p = NULL;
1005  // @todo not possible to initialize a routing oplan based on a start date
1006  if (d != Date::infiniteFuture)
1007  {
1008  // Using the end date
1009  for (Operation::Operationlist::const_reverse_iterator e =
1010  getSubOperations().rbegin(); e != getSubOperations().rend(); ++e)
1011  {
1012  p = (*e)->createOperationPlan(o->getQuantity(), Date::infinitePast,
1013  d, NULL, o, 0, true);
1014  d = p->getDates().getStart();
1015  }
1016  }
1017  else
1018  {
1019  // Using the start date when there is no end date
1020  d = o->getDates().getStart();
1021  // Using the current date when both the start and end date are missing
1022  if (!d) d = Plan::instance().getCurrent();
1023  for (Operation::Operationlist::const_iterator e =
1024  getSubOperations().begin(); e != getSubOperations().end(); ++e)
1025  {
1026  p = (*e)->createOperationPlan(o->getQuantity(), d,
1027  Date::infinitePast, NULL, o, 0, true);
1028  d = p->getDates().getEnd();
1029  }
1030  }
1031  }
1032  return true;
1033 }
1034 
1035 
1037 {
1038  if (c == "PRIORITY") return PRIORITY;
1039  if (c == "MINCOST") return MINCOST;
1040  if (c == "MINPENALTY") return MINPENALTY;
1041  if (c == "MINCOSTPENALTY") return MINCOSTPENALTY;
1042  throw DataException("Invalid search mode " + c);
1043 }
1044 
1045 
1047 (Operation* o, int prio, DateRange eff)
1048 {
1049  if (!o) return;
1050  Operationlist::iterator altIter = alternates.begin();
1051  alternatePropertyList::iterator propIter = alternateProperties.begin();
1052  while (altIter!=alternates.end() && prio >= propIter->first)
1053  {
1054  ++propIter;
1055  ++altIter;
1056  }
1057  alternateProperties.insert(propIter,alternateProperty(prio,eff));
1058  alternates.insert(altIter,o);
1059  o->addSuperOperation(this);
1060 }
1061 
1062 
1065 {
1066  if (!o)
1067  throw LogicException("Null pointer passed when searching for a \
1068  suboperation of alternate operation '" + getName() + "'");
1069  Operationlist::const_iterator altIter = alternates.begin();
1070  alternatePropertyList::const_iterator propIter = alternateProperties.begin();
1071  while (altIter!=alternates.end() && *altIter != o)
1072  {
1073  ++propIter;
1074  ++altIter;
1075  }
1076  if (*altIter == o) return *propIter;
1077  throw DataException("Operation '" + o->getName() +
1078  "' isn't a suboperation of alternate operation '" + getName() + "'");
1079 }
1080 
1081 
1083 {
1084  if (!o) return;
1085  Operationlist::const_iterator altIter = alternates.begin();
1086  alternatePropertyList::iterator propIter = alternateProperties.begin();
1087  while (altIter!=alternates.end() && *altIter != o)
1088  {
1089  ++propIter;
1090  ++altIter;
1091  }
1092  if (*altIter == o)
1093  propIter->first = f;
1094  else
1095  throw DataException("Operation '" + o->getName() +
1096  "' isn't a suboperation of alternate operation '" + getName() + "'");
1097 }
1098 
1099 
1101 {
1102  if (!o) return;
1103  Operationlist::const_iterator altIter = alternates.begin();
1104  alternatePropertyList::iterator propIter = alternateProperties.begin();
1105  while (altIter!=alternates.end() && *altIter != o)
1106  {
1107  ++propIter;
1108  ++altIter;
1109  }
1110  if (*altIter == o)
1111  propIter->second = dr;
1112  else
1113  throw DataException("Operation '" + o->getName() +
1114  "' isn't a suboperation of alternate operation '" + getName() + "'");
1115 }
1116 
1117 
1119 (XMLOutput *o, const Keyword& tag, mode m) const
1120 {
1121  // Writing a reference
1122  if (m == REFERENCE)
1123  {
1124  o->writeElement
1125  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
1126  return;
1127  }
1128 
1129  // Write the complete object
1130  if (m != NOHEADER) o->BeginObject
1131  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
1132 
1133  // Write the standard fields
1135  if (search != PRIORITY)
1136  o->writeElement(Tags::tag_search, search);
1137 
1138  // Write the extra fields
1140  alternatePropertyList::const_iterator propIter = alternateProperties.begin();
1141  for (Operationlist::const_iterator i = alternates.begin();
1142  i != alternates.end(); ++i)
1143  {
1146  o->writeElement(Tags::tag_priority, propIter->first);
1147  if (propIter->second.getStart() != Date::infinitePast)
1148  o->writeElement(Tags::tag_effective_start, propIter->second.getStart());
1149  if (propIter->second.getEnd() != Date::infiniteFuture)
1150  o->writeElement(Tags::tag_effective_end, propIter->second.getEnd());
1152  ++propIter;
1153  }
1155 
1156  // Ending tag
1157  o->EndObject(tag);
1158 }
1159 
1160 
1162 {
1163  if (pAttr.isA(Tags::tag_operation))
1165  else
1166  Operation::beginElement(pIn, pAttr);
1167 }
1168 
1169 
1171 {
1172  // Saving some typing...
1173  typedef pair<Operation*,alternateProperty> tempData;
1174 
1175  // Create a temporary object
1176  if (!pIn.getUserArea())
1177  pIn.setUserArea(new tempData(static_cast<Operation*>(NULL),alternateProperty(1,DateRange())));
1178  tempData* tmp = static_cast<tempData*>(pIn.getUserArea());
1179 
1180  if (pAttr.isA(Tags::tag_alternate))
1181  {
1182  addAlternate(tmp->first, tmp->second.first, tmp->second.second);
1183  // Reset the defaults
1184  tmp->first = NULL;
1185  tmp->second.first = 1;
1186  tmp->second.second = DateRange();
1187  }
1188  else if (pAttr.isA(Tags::tag_priority))
1189  tmp->second.first = pElement.getInt();
1190  else if (pAttr.isA(Tags::tag_search))
1191  setSearch(pElement.getString());
1192  else if (pAttr.isA(Tags::tag_effective_start))
1193  tmp->second.second.setStart(pElement.getDate());
1194  else if (pAttr.isA(Tags::tag_effective_end))
1195  tmp->second.second.setEnd(pElement.getDate());
1196  else if (pAttr.isA(Tags::tag_operation)
1197  && pIn.getParentElement().first.isA(Tags::tag_alternate))
1198  {
1199  Operation * b = dynamic_cast<Operation*>(pIn.getPreviousObject());
1200  if (b) tmp->first = b;
1201  else throw LogicException("Incorrect object type during read operation");
1202  }
1203  Operation::endElement (pIn, pAttr, pElement);
1204 
1205  // Delete the temporary object
1206  if (pIn.isObjectEnd()) delete static_cast<tempData*>(pIn.getUserArea());
1207 }
1208 
1209 
1212 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd,
1213  bool execute) const
1214 {
1215  // Invalid calls to this function
1216  if (!opplan || q<0)
1217  throw LogicException("Incorrect parameters for alternate operationplan");
1218  if (opplan->getLocked())
1219  return OperationPlanState(opplan);
1220 
1221  OperationPlan *x = opplan->lastsubopplan;
1222  while (x && x->getOperation() == OperationSetup::setupoperation)
1223  x = x->prevsubopplan;
1224  if (!x)
1225  {
1226  // Blindly accept the parameters if there is no suboperationplan
1227  if (execute)
1228  {
1229  opplan->setQuantity(q,false,false);
1230  opplan->setStartAndEnd(s, e);
1231  return OperationPlanState(opplan);
1232  }
1233  else
1234  return OperationPlanState(s, e, opplan->setQuantity(q,false,false,false));
1235  }
1236  else
1237  // Pass the call to the sub-operation
1238  return x->getOperation()
1239  ->setOperationPlanParameters(x,q,s,e,preferEnd, execute);
1240 }
1241 
1242 
1244 {
1245  // Create a suboperationplan if one doesn't exist yet.
1246  // We use the first effective alternate by default.
1247  if (!o->lastsubopplan || o->lastsubopplan->getOperation() == OperationSetup::setupoperation)
1248  {
1249  // Find the right operation
1250  Operationlist::const_iterator altIter = getSubOperations().begin();
1251  for (; altIter != getSubOperations().end(); )
1252  {
1253  const OperationAlternate::alternateProperty& props = getProperties(*altIter);
1254  // Filter out alternates that are not suitable
1255  if (props.first != 0.0 && props.second.within(o->getDates().getEnd()))
1256  break;
1257  }
1258  if (altIter != getSubOperations().end())
1259  // Create an operationplan instance
1260  (*altIter)->createOperationPlan(
1261  o->getQuantity(), o->getDates().getStart(),
1262  o->getDates().getEnd(), NULL, o, 0, true);
1263  }
1264  return true;
1265 }
1266 
1267 
1269 {
1270  Operationlist::iterator altIter = alternates.begin();
1271  alternatePropertyList::iterator propIter = alternateProperties.begin();
1272  while (altIter!=alternates.end() && *altIter != o)
1273  {
1274  ++propIter;
1275  ++altIter;
1276  }
1277  if (*altIter == o)
1278  {
1279  alternates.erase(altIter);
1280  alternateProperties.erase(propIter);
1281  o->superoplist.remove(this);
1282  setChanged();
1283  }
1284  else
1285  logger << "Warning: operation '" << *o
1286  << "' isn't a suboperation of alternate operation '" << *this
1287  << "'" << endl;
1288 }
1289 
1290 
1292 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
1293 {
1294  // Find or create a loadplan
1296  LoadPlan *ldplan = NULL;
1297  if (i != opplan->endLoadPlans())
1298  // Already exists
1299  ldplan = &*i;
1300  else
1301  {
1302  // Create a new one
1303  if (!opplan->getOwner())
1304  throw LogicException("Setup operationplan always must have an owner");
1305  for (loadlist::const_iterator g=opplan->getOwner()->getOperation()->getLoads().begin();
1306  g!=opplan->getOwner()->getOperation()->getLoads().end(); ++g)
1307  if (g->getResource()->getSetupMatrix() && !g->getSetup().empty())
1308  {
1309  ldplan = new LoadPlan(opplan, &*g);
1310  break;
1311  }
1312  if (!ldplan)
1313  throw LogicException("Can't find a setup on operation '"
1314  + opplan->getOwner()->getOperation()->getName() + "'");
1315  }
1316 
1317  // Find the setup of the resource at the start of the conversion
1318  const Load* lastld = NULL;
1319  Date boundary = s ? s : e;
1320  if (ldplan->getDate() < boundary)
1321  {
1322  for (TimeLine<LoadPlan>::const_iterator i = ldplan->getResource()->getLoadPlans().begin(ldplan);
1323  i != ldplan->getResource()->getLoadPlans().end() && i->getDate() <= boundary; ++i)
1324  {
1325  const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i);
1326  if (l && i->getQuantity() != 0.0
1327  && l->getOperationPlan() != opplan
1328  && l->getOperationPlan() != opplan->getOwner()
1329  && !l->getLoad()->getSetup().empty())
1330  lastld = l->getLoad();
1331  }
1332  }
1333  if (!lastld)
1334  {
1335  for (TimeLine<LoadPlan>::const_iterator i = ldplan->getResource()->getLoadPlans().begin(ldplan);
1336  i != ldplan->getResource()->getLoadPlans().end(); --i)
1337  {
1338  const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i);
1339  if (l && i->getQuantity() != 0.0
1340  && l->getOperationPlan() != opplan
1341  && l->getOperationPlan() != opplan->getOwner()
1342  && !l->getLoad()->getSetup().empty()
1343  && l->getDate() <= boundary)
1344  {
1345  lastld = l->getLoad();
1346  break;
1347  }
1348  }
1349  }
1350  string lastsetup = lastld ? lastld->getSetup() : ldplan->getResource()->getSetup();
1351 
1352  TimePeriod duration(0L);
1353  if (lastsetup != ldplan->getLoad()->getSetup())
1354  {
1355  // Calculate the setup time
1356  SetupMatrix::Rule *conversionrule = ldplan->getLoad()->getResource()->getSetupMatrix()
1357  ->calculateSetup(lastsetup, ldplan->getLoad()->getSetup());
1358  duration = conversionrule ? conversionrule->getDuration() : TimePeriod(365L*86400L);
1359  }
1360 
1361  // Set the start and end date.
1362  DateRange x;
1363  TimePeriod actualduration;
1364  if (e && s)
1365  {
1366  if (preferEnd) x = calculateOperationTime(e, duration, false, &actualduration);
1367  else x = calculateOperationTime(s, duration, true, &actualduration);
1368  }
1369  else if (s) x = calculateOperationTime(s, duration, true, &actualduration);
1370  else x = calculateOperationTime(e, duration, false, &actualduration);
1371  if (!execute)
1372  // Simulation only
1373  return OperationPlanState(x, actualduration == duration ? q : 0);
1374  else if (actualduration == duration)
1375  {
1376  // Update succeeded
1377  opplan->setStartAndEnd(x.getStart(), x.getEnd());
1378  if (opplan->getOwner()->getDates().getStart() != opplan->getDates().getEnd())
1379  opplan->getOwner()->setStart(opplan->getDates().getEnd());
1380  }
1381  else
1382  // Update failed - Not enough available time @todo setting the qty to 0 is not enough
1383  opplan->setQuantity(0);
1384 
1385  return OperationPlanState(opplan);
1386 }
1387 
1388 
1390 {
1391  if (attr.isA(Tags::tag_name))
1392  return PythonObject(getName());
1393  if (attr.isA(Tags::tag_description))
1394  return PythonObject(getDescription());
1395  if (attr.isA(Tags::tag_category))
1396  return PythonObject(getCategory());
1397  if (attr.isA(Tags::tag_subcategory))
1398  return PythonObject(getSubCategory());
1399  if (attr.isA(Tags::tag_location))
1400  return PythonObject(getLocation());
1401  if (attr.isA(Tags::tag_fence))
1402  return PythonObject(getFence());
1403  if (attr.isA(Tags::tag_size_minimum))
1404  return PythonObject(getSizeMinimum());
1405  if (attr.isA(Tags::tag_size_multiple))
1406  return PythonObject(getSizeMultiple());
1407  if (attr.isA(Tags::tag_size_maximum))
1408  return PythonObject(getSizeMaximum());
1409  if (attr.isA(Tags::tag_cost))
1410  return PythonObject(getCost());
1411  if (attr.isA(Tags::tag_pretime))
1412  return PythonObject(getPreTime());
1413  if (attr.isA(Tags::tag_posttime))
1414  return PythonObject(getPostTime());
1415  if (attr.isA(Tags::tag_hidden))
1416  return PythonObject(getHidden());
1417  if (attr.isA(Tags::tag_loads))
1418  return new LoadIterator(this);
1419  if (attr.isA(Tags::tag_flows))
1420  return new FlowIterator(this);
1421  if (attr.isA(Tags::tag_operationplans))
1422  return new OperationPlanIterator(this);
1423  if (attr.isA(Tags::tag_level))
1424  return PythonObject(getLevel());
1425  if (attr.isA(Tags::tag_cluster))
1426  return PythonObject(getCluster());
1427  return NULL;
1428 }
1429 
1430 
1432 {
1433  if (attr.isA(Tags::tag_name))
1434  setName(field.getString());
1435  else if (attr.isA(Tags::tag_description))
1436  setDescription(field.getString());
1437  else if (attr.isA(Tags::tag_category))
1438  setCategory(field.getString());
1439  else if (attr.isA(Tags::tag_subcategory))
1440  setSubCategory(field.getString());
1441  else if (attr.isA(Tags::tag_location))
1442  {
1443  if (!field.check(Location::metadata))
1444  {
1445  PyErr_SetString(PythonDataException, "buffer location must be of type location");
1446  return -1;
1447  }
1448  Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
1449  setLocation(y);
1450  }
1451  else if (attr.isA(Tags::tag_fence))
1452  setFence(field.getTimeperiod());
1453  else if (attr.isA(Tags::tag_size_minimum))
1454  setSizeMinimum(field.getDouble());
1455  else if (attr.isA(Tags::tag_size_multiple))
1456  setSizeMultiple(field.getDouble());
1457  else if (attr.isA(Tags::tag_size_maximum))
1458  setSizeMaximum(field.getDouble());
1459  else if (attr.isA(Tags::tag_cost))
1460  setCost(field.getDouble());
1461  else if (attr.isA(Tags::tag_pretime))
1462  setPreTime(field.getTimeperiod());
1463  else if (attr.isA(Tags::tag_posttime))
1464  setPostTime(field.getTimeperiod());
1465  else if (attr.isA(Tags::tag_hidden))
1466  setHidden(field.getBool());
1467  else
1468  return -1; // Error
1469  return 0; // OK
1470 }
1471 
1472 
1474 {
1475  if (attr.isA(Tags::tag_duration))
1476  return PythonObject(getDuration());
1477  return Operation::getattro(attr);
1478 }
1479 
1480 
1482 {
1483  if (attr.isA(Tags::tag_duration))
1484  setDuration(field.getTimeperiod());
1485  else
1486  return Operation::setattro(attr, field);
1487  return 0;
1488 }
1489 
1490 
1492 {
1493  if (attr.isA(Tags::tag_duration))
1494  return PythonObject(getDuration());
1495  if (attr.isA(Tags::tag_duration_per))
1496  return PythonObject(getDurationPer());
1497  return Operation::getattro(attr);
1498 }
1499 
1500 
1502 {
1503  if (attr.isA(Tags::tag_duration))
1504  setDuration(field.getTimeperiod());
1505  else if (attr.isA(Tags::tag_duration_per))
1506  setDurationPer(field.getTimeperiod());
1507  else
1508  return Operation::setattro(attr, field);
1509  return 0;
1510 }
1511 
1512 
1514 {
1515  if (attr.isA(Tags::tag_alternates))
1516  {
1517  PyObject* result = PyTuple_New(getSubOperations().size());
1518  int count = 0;
1519  for (Operation::Operationlist::const_iterator i = getSubOperations().begin(); i != getSubOperations().end(); ++i)
1520  PyTuple_SetItem(result, count++, PythonObject(*i));
1521  return result;
1522  }
1523  if (attr.isA(Tags::tag_search))
1524  {
1525  ostringstream ch;
1526  ch << getSearch();
1527  return PythonObject(ch.str());
1528  }
1529  return Operation::getattro(attr);
1530 }
1531 
1532 
1534 {
1535  if (attr.isA(Tags::tag_search))
1536  setSearch(field.getString());
1537  else
1538  return Operation::setattro(attr, field);
1539  return 0;
1540 }
1541 
1542 
1543 DECLARE_EXPORT PyObject* OperationAlternate::addAlternate(PyObject* self, PyObject* args, PyObject* kwdict)
1544 {
1545  try
1546  {
1547  // Pick up the alternate operation
1548  OperationAlternate *altoper = static_cast<OperationAlternate*>(self);
1549  if (!altoper) throw LogicException("Can't add alternates to NULL alternate");
1550 
1551  // Parse the arguments
1552  PyObject *oper = NULL;
1553  int prio = 1;
1554  PyObject *eff_start = NULL;
1555  PyObject *eff_end = NULL;
1556  static const char *kwlist[] = {"operation", "priority", "effective_start", "effective_end", NULL};
1557  if (!PyArg_ParseTupleAndKeywords(args, kwdict,
1558  "O|iOO:addAlternate",
1559  const_cast<char**>(kwlist), &oper, &prio, &eff_start, &eff_end))
1560  return NULL;
1561  if (!PyObject_TypeCheck(oper, Operation::metadata->pythonClass))
1562  throw DataException("alternate operation must be of type operation");
1563  DateRange eff;
1564  if (eff_start)
1565  {
1566  PythonObject d(eff_start);
1567  eff.setStart(d.getDate());
1568  }
1569  if (eff_end)
1570  {
1571  PythonObject d(eff_end);
1572  eff.setEnd(d.getDate());
1573  }
1574 
1575  // Add the alternate
1576  altoper->addAlternate(static_cast<Operation*>(oper), prio, eff);
1577  }
1578  catch(...)
1579  {
1580  PythonType::evalException();
1581  return NULL;
1582  }
1583  return Py_BuildValue("");
1584 }
1585 
1586 
1588 {
1589  if (attr.isA(Tags::tag_steps))
1590  {
1591  PyObject* result = PyTuple_New(getSubOperations().size());
1592  int count = 0;
1593  for (Operation::Operationlist::const_iterator i = getSubOperations().begin(); i != getSubOperations().end(); ++i)
1594  PyTuple_SetItem(result, count++, PythonObject(*i));
1595  return result;
1596  }
1597  return Operation::getattro(attr);
1598 }
1599 
1600 
1601 PyObject *OperationRouting::addStep(PyObject *self, PyObject *args)
1602 {
1603  try
1604  {
1605  // Pick up the routing operation
1606  OperationRouting *oper = static_cast<OperationRouting*>(self);
1607  if (!oper) throw LogicException("Can't add steps to NULL routing");
1608 
1609  // Parse the arguments
1610  PyObject *steps[4];
1611  for (unsigned int i=0; i<4; ++i) steps[i] = NULL;
1612  if (PyArg_UnpackTuple(args, "addStep", 1, 4, &steps[0], &steps[1], &steps[2], &steps[3]))
1613  for (unsigned int i=0; i<4 && steps[i]; ++i)
1614  {
1615  if (!PyObject_TypeCheck(steps[i], Operation::metadata->pythonClass))
1616  throw DataException("routing steps must be of type operation");
1617  oper->addStepBack(static_cast<Operation*>(steps[i]));
1618  }
1619  }
1620  catch(...)
1621  {
1622  PythonType::evalException();
1623  return NULL;
1624  }
1625  return Py_BuildValue("");
1626 }
1627 
1628 } // end namespace