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