void Operation::updateProblems() { // Find all operationplans, and delegate the problem detection to them if (getDetectProblems()) for (OperationPlan *o = first_opplan; o; o = o->next) o->updateProblems(); }
DECLARE_EXPORT PeggingIterator::PeggingIterator(const Demand* d) : downstream(false), firstIteration(true), first(false) { initType(metadata); const Demand::OperationPlanList &deli = d->getDelivery(); for (Demand::OperationPlanList::const_iterator opplaniter = deli.begin(); opplaniter != deli.end(); ++opplaniter) { OperationPlan *t = (*opplaniter)->getTopOwner(); updateStack(t, t->getQuantity(), 0.0, 0); } }
DECLARE_EXPORT void Buffer::setOnHand(double f) { // The dummy operation to model the inventory may need to be created Operation *o = Operation::find(INVENTORY_OPERATION); Flow *fl; if (!o) { // Create a fixed time operation with zero leadtime, hidden from the xml // output, hidden for the solver, and without problem detection. o = new OperationFixedTime(); o->setName(INVENTORY_OPERATION); o->setHidden(true); o->setDetectProblems(false); fl = new FlowEnd(o, this, 1); } else // Find the flow of this operation fl = const_cast<Flow*>(&*(o->getFlows().begin())); // Check valid pointers if (!fl || !o) throw LogicException("Failed creating inventory operation for '" + getName() + "'"); // Make sure the sign of the flow is correct: +1 or -1. fl->setQuantity(f>=0.0 ? 1.0 : -1.0); // Create a dummy operationplan on the inventory operation OperationPlan::iterator i(o); if (i == OperationPlan::end()) { // No operationplan exists yet OperationPlan *opplan = o->createOperationPlan( fabs(f), Date::infinitePast, Date::infinitePast); opplan->setLocked(true); opplan->activate(); } else { // Update the existing operationplan i->setLocked(false); i->setQuantity(fabs(f)); i->setLocked(true); } setChanged(); }
PeggingIterator::PeggingIterator(const Demand* d) : downstream(false), firstIteration(true), first(false), second_pass(false) { initType(metadata); const Demand::OperationPlanList &deli = d->getDelivery(); for (Demand::OperationPlanList::const_iterator opplaniter = deli.begin(); opplaniter != deli.end(); ++opplaniter) { OperationPlan *t = (*opplaniter)->getTopOwner(); updateStack(t, t->getQuantity(), 0.0, 0); } // Bring all pegging information to a second stack. // Only in this way can we avoid that the same operationplan is returned // multiple times while (operator bool()) { /** Check if already found in the vector. */ bool found = false; state& curtop = states.back(); for (deque<state>::iterator it = states_sorted.begin(); it != states_sorted.end() && !found; ++it) if (it->opplan == curtop.opplan) { // Update existing element in sorted stack it->quantity += curtop.quantity; if (it->level > curtop.level) it->level = curtop.level; found = true; } if (!found) // New element in sorted stack states_sorted.push_back( state(curtop.opplan, curtop.quantity, curtop.offset, curtop.level) ); if (downstream) ++*this; else --*this; } // The normal iteration will use the sorted results second_pass = true; }
DECLARE_EXPORT void Buffer::followPegging (PeggingIterator& iter, FlowPlan* curflowplan, double qty, double offset, short lvl) { if (!curflowplan->getOperationPlan()->getQuantity() || curflowplan->getBuffer()->getTool()) // Flowplans with quantity 0 have no pegging. // Flowplans for buffers representing tools have no pegging either. return; Buffer::flowplanlist::iterator f = getFlowPlans().begin(curflowplan); if (curflowplan->getQuantity() < -ROUNDING_ERROR && !iter.isDownstream()) { // CASE 1: // This is a flowplan consuming from a buffer. Navigating upstream means // finding the flowplans producing this consumed material. double scale = - curflowplan->getQuantity() / curflowplan->getOperationPlan()->getQuantity(); double startQty = f->getCumulativeConsumed() + f->getQuantity() + offset * scale; double endQty = startQty + qty * scale; if (f->getCumulativeProduced() <= startQty + ROUNDING_ERROR) { // CASE 1A: Not produced enough yet: move forward while (f!=getFlowPlans().end() && f->getCumulativeProduced() <= startQty) ++f; while (f!=getFlowPlans().end() && ( (f->getQuantity()<=0 && f->getCumulativeProduced() < endQty) || (f->getQuantity()>0 && f->getCumulativeProduced()-f->getQuantity() < endQty)) ) { if (f->getQuantity() > ROUNDING_ERROR) { double newqty = f->getQuantity(); double newoffset = 0.0; if (f->getCumulativeProduced()-f->getQuantity() < startQty) { newoffset = startQty - (f->getCumulativeProduced()-f->getQuantity()); newqty -= newoffset; } if (f->getCumulativeProduced() > endQty) newqty -= f->getCumulativeProduced() - endQty; OperationPlan *opplan = dynamic_cast<const FlowPlan*>(&(*f))->getOperationPlan(); OperationPlan *topopplan = opplan->getTopOwner(); if (topopplan->getOperation()->getType() == *OperationSplit::metadata) topopplan = opplan; iter.updateStack( topopplan, topopplan->getQuantity() * newqty / f->getQuantity(), topopplan->getQuantity() * newoffset / f->getQuantity(), lvl ); } ++f; } } else { // CASE 1B: Produced too much already: move backward while ( f!=getFlowPlans().end() && ((f->getQuantity()<=0 && f->getCumulativeProduced() > endQty) || (f->getQuantity()>0 && f->getCumulativeProduced()-f->getQuantity() > endQty))) --f; while (f!=getFlowPlans().end() && f->getCumulativeProduced() > startQty) { if (f->getQuantity() > ROUNDING_ERROR) { double newqty = f->getQuantity(); double newoffset = 0.0; if (f->getCumulativeProduced()-f->getQuantity() < startQty) { newoffset = startQty - (f->getCumulativeProduced()-f->getQuantity()); newqty -= newoffset; } if (f->getCumulativeProduced() > endQty) newqty -= f->getCumulativeProduced() - endQty; OperationPlan *opplan = dynamic_cast<FlowPlan*>(&(*f))->getOperationPlan(); OperationPlan *topopplan = opplan->getTopOwner(); if (topopplan->getOperation()->getType() == *OperationSplit::metadata) topopplan = opplan; iter.updateStack( topopplan, topopplan->getQuantity() * newqty / f->getQuantity(), topopplan->getQuantity() * newoffset / f->getQuantity(), lvl ); } --f; } } return; } if (curflowplan->getQuantity() > ROUNDING_ERROR && iter.isDownstream()) { // CASE 2: // This is a flowplan producing in a buffer. Navigating downstream means // finding the flowplans consuming this produced material. double scale = curflowplan->getQuantity() / curflowplan->getOperationPlan()->getQuantity(); double startQty = f->getCumulativeProduced() - f->getQuantity() + offset * scale; double endQty = startQty + qty * scale; if (f->getCumulativeConsumed() <= startQty + ROUNDING_ERROR) { // CASE 2A: Not consumed enough yet: move forward while (f!=getFlowPlans().end() && f->getCumulativeConsumed() <= startQty) ++f; while (f!=getFlowPlans().end() && ( (f->getQuantity()<=0 && f->getCumulativeConsumed()+f->getQuantity() < endQty) || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty)) ) { if (f->getQuantity() < -ROUNDING_ERROR) { double newqty = - f->getQuantity(); double newoffset = 0.0; if (f->getCumulativeConsumed()+f->getQuantity() < startQty) { newoffset = startQty - (f->getCumulativeConsumed()+f->getQuantity()); newqty -= newoffset; } if (f->getCumulativeConsumed() > endQty) newqty -= f->getCumulativeConsumed() - endQty; OperationPlan *opplan = dynamic_cast<FlowPlan*>(&(*f))->getOperationPlan(); OperationPlan *topopplan = opplan->getTopOwner(); if (topopplan->getOperation()->getType() == *OperationSplit::metadata) topopplan = opplan; iter.updateStack( topopplan, - topopplan->getQuantity() * newqty / f->getQuantity(), - topopplan->getQuantity() * newoffset / f->getQuantity(), lvl ); } ++f; } } else { // CASE 2B: Consumed too much already: move backward while ( f!=getFlowPlans().end() && ((f->getQuantity()<=0 && f->getCumulativeConsumed()+f->getQuantity() < endQty) || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))) --f; while (f!=getFlowPlans().end() && f->getCumulativeConsumed() > startQty) { if (f->getQuantity() < -ROUNDING_ERROR) { double newqty = - f->getQuantity(); double newoffset = 0.0; if (f->getCumulativeConsumed()+f->getQuantity() < startQty) newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity()); if (f->getCumulativeConsumed() > endQty) newqty -= f->getCumulativeConsumed() - endQty; OperationPlan *opplan = dynamic_cast<FlowPlan*>(&(*f))->getOperationPlan(); OperationPlan *topopplan = opplan->getTopOwner(); if (topopplan->getOperation()->getType() == *OperationSplit::metadata) topopplan = opplan; iter.updateStack( topopplan, - topopplan->getQuantity() * newqty / f->getQuantity(), - topopplan->getQuantity() * newoffset / f->getQuantity(), lvl ); } --f; } } } }
int main (int argc, char *argv[]) { try { // 0: Initialize FreppleInitialize(); // 1: Read the model FreppleReadXMLFile("problems.xml",true,false); reportProblems("reading input"); // 2: Plan the model FreppleReadXMLData( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \ "<plan xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" \ "<?python\n" \ "frepple.solver_mrp(name=\"MRP\", constraints=0).solve()\n" \ "?>\n" \ "</plan>", true, false ); reportProblems("planning"); // Define variables for each of the 2 operation_plans Operation *buildoper = Operation::find("make end item"); OperationPlan *build = &*OperationPlan::iterator(buildoper); Operation *deliveroper = Operation::find("delivery end item"); OperationPlan *deliver = &*OperationPlan::iterator(deliveroper); if (!deliver || !build) throw DataException("Can't find operationplans"); // 3: Increase quantity of the delivery & report float oldqty = deliver->getQuantity(); deliver->setQuantity(100); reportProblems("increasing delivery quantity"); // 4: Reduce the quantity of the delivey & report deliver->setQuantity(1); reportProblems("decreasing delivery quantity"); // 5: Move the delivery early & report Date oldstart = deliver->getDates().getStart(); deliver->setStart(oldstart - Duration(86400)); reportProblems("moving delivery early"); // 6: Move the delivery late & report deliver->setStart(oldstart + Duration(86400)); reportProblems("moving delivery late"); // 7: Restoring original delivery plan & report deliver->setQuantity(oldqty); deliver->setStart(oldstart); reportProblems("restoring original delivery plan"); // 8: Deleting delivery delete deliver; reportProblems("deleting delivery plan"); // 9: Move the make operation before current & report oldstart = build->getDates().getStart(); build->setStart(Plan::instance().getCurrent() - Duration(1)); reportProblems("moving build early"); // 10: Restoring the original build plan & report build->setStart(oldstart); reportProblems("restoring original build plan"); } catch (...) { logger << "Error: Caught an exception in main routine:" << endl; try { throw; } catch (const exception& e) {logger << " " << e.what() << endl;} catch (...) {logger << " Unknown type" << endl;} return EXIT_FAILURE; } return EXIT_SUCCESS; }
extern "C" PyObject* OperationItemSupplier::createOrder( PyObject *self, PyObject *args, PyObject *kwdict ) { // Parse the Python arguments PyObject* pylocation = NULL; unsigned long id = 0; const char* ref = NULL; PyObject* pyitem = NULL; PyObject* pysupplier = NULL; double qty = 0; PyObject* pystart = NULL; PyObject* pyend = NULL; const char* status = NULL; const char* source = NULL; static const char *kwlist[] = { "location", "id", "reference", "item", "supplier", "quantity", "start", "end", "status", "source", NULL }; int ok = PyArg_ParseTupleAndKeywords( args, kwdict, "|OkzOOdOOzz:createOrder", const_cast<char**>(kwlist), &pylocation, &id, &ref, &pyitem, &pysupplier, &qty, &pystart, &pyend, &status, &source ); if (!ok) return NULL; Date start = pystart ? PythonData(pystart).getDate() : Date::infinitePast; Date end = pyend ? PythonData(pyend).getDate() : Date::infinitePast; // Validate all arguments if (!pylocation || !pyitem) { PyErr_SetString(PythonDataException, "item and location arguments are mandatory"); return NULL; } PythonData location_tmp(pylocation); if (!location_tmp.check(Location::metadata)) { PyErr_SetString(PythonDataException, "location argument must be of type location"); return NULL; } PythonData item_tmp(pyitem); if (!item_tmp.check(Item::metadata)) { PyErr_SetString(PythonDataException, "item argument must be of type item"); return NULL; } PythonData supplier_tmp(pysupplier); if (pysupplier && !supplier_tmp.check(Supplier::metadata)) { PyErr_SetString(PythonDataException, "supplier argument must be of type supplier"); return NULL; } Item *item = static_cast<Item*>(item_tmp.getObject()); Location *location = static_cast<Location*>(location_tmp.getObject()); Supplier *supplier = pysupplier ? static_cast<Supplier*>(supplier_tmp.getObject()) : NULL; // Find or create the destination buffer. Buffer* destbuffer = NULL; Item::bufferIterator buf_iter(item); while (Buffer* tmpbuf = buf_iter.next()) { if (tmpbuf->getLocation() == location) { if (destbuffer) { stringstream o; o << "Multiple buffers found for item '" << item << "'' and location'" << location << "'"; throw DataException(o.str()); } destbuffer = tmpbuf; } } if (!destbuffer) { // Create the destination buffer destbuffer = new BufferDefault(); stringstream o; o << item << " @ " << location; destbuffer->setName(o.str()); destbuffer->setItem(item); destbuffer->setLocation(location); } // Build the producing operation for this buffer. destbuffer->getProducingOperation(); // Look for a matching operation replenishing this buffer. Operation *oper = NULL; for (Buffer::flowlist::const_iterator flowiter = destbuffer->getFlows().begin(); flowiter != destbuffer->getFlows().end() && !oper; ++flowiter) { if (flowiter->getOperation()->getType() != *OperationItemSupplier::metadata) continue; OperationItemSupplier* opitemsupplier = static_cast<OperationItemSupplier*>(flowiter->getOperation()); if (supplier) { if (supplier->isMemberOf(opitemsupplier->getItemSupplier()->getSupplier())) oper = opitemsupplier; } else oper = opitemsupplier; } // No matching operation is found. if (!oper) { // We'll create one now, but that requires that we have a supplier defined. if (!supplier) throw DataException("Supplier is needed on this purchase order"); // Note: We know that we need to create a new one. An existing one would // have created an operation on the buffer already. ItemSupplier *itemsupplier = new ItemSupplier(); itemsupplier->setSupplier(supplier); itemsupplier->setItem(item); itemsupplier->setLocation(location); oper = new OperationItemSupplier(itemsupplier, destbuffer); new ProblemInvalidData(oper, "Purchase orders on unauthorized supplier", "operation", Date::infinitePast, Date::infiniteFuture, 1); } // Finally, create the operationplan OperationPlan *opplan = oper->createOperationPlan(qty, start, end); if (id) opplan->setRawIdentifier(id); // We can use this fast method because we call activate later if (status) opplan->setStatus(status); // Reset quantity after the status update to assure that // also non-valid quantities are getting accepted. opplan->setQuantity(qty); if (ref) opplan->setReference(ref); opplan->activate(); // Return result Py_INCREF(opplan); return opplan; }
DECLARE_EXPORT void LoadPlan::setResource(Resource* newres, bool check) { // Nothing to do if (res == newres) return; // Validate the argument if (!newres) throw DataException("Can't switch to NULL resource"); if (check) { // New resource must be a subresource of the load's resource. bool ok = false; for (const Resource* i = newres; i && !ok; i = i->getOwner()) if (i == getLoad()->getResource()) ok = true; if (!ok) throw DataException("Resource isn't matching the resource specified on the load"); // New resource must have the required skill if (getLoad()->getSkill()) { ok = false; for(Resource::skilllist::const_iterator s = newres->getSkills().begin(); s != newres->getSkills().end() && !ok; s++) if (s->getSkill() == getLoad()->getSkill()) ok = true; if (!ok) throw DataException("Resource misses the skill specified on the load"); } } // Mark entities as changed if (oper) oper->getOperation()->setChanged(); if (res && res!=newres) res->setChanged(); newres->setChanged(); // Update also the setup operationplans if (oper && oper->getOperation() != OperationSetup::setupoperation) { bool oldHasSetup = ld && !ld->getSetup().empty() // TODO not fully correct. If the load is changed, it is still possible that the old load had a setup, while ld doesn't have one any more... && res && res->getSetupMatrix(); bool newHasSetup = ld && !ld->getSetup().empty() && newres->getSetupMatrix(); OperationPlan *setupOpplan = NULL; if (oldHasSetup) { for (OperationPlan::iterator i(oper); i != oper->end(); ++i) if (i->getOperation() == OperationSetup::setupoperation) { setupOpplan = &*i; break; } if (!setupOpplan) oldHasSetup = false; } if (oldHasSetup) { if (newHasSetup) { // Case 1: Both the old and new load require a setup LoadPlan *setupLdplan = NULL; for (OperationPlan::LoadPlanIterator j = setupOpplan->beginLoadPlans(); j != setupOpplan->endLoadPlans(); ++j) if (j->getLoad() == ld) { setupLdplan = &*j; break; } if (!setupLdplan) throw LogicException("Can't find loadplan on setup operationplan"); // Update the loadplan setupOpplan->setEnd(setupOpplan->getDates().getEnd()); } else { // Case 2: Delete the old setup which is not required any more oper->eraseSubOperationPlan(setupOpplan); } } else { if (newHasSetup) { // Case 3: Create a new setup operationplan OperationSetup::setupoperation->createOperationPlan( 1, Date::infinitePast, oper->getDates().getEnd(), NULL, oper); } //else: // Case 4: No setup for the old or new load } } // Find the loadplan before the setup LoadPlan *prevldplan = NULL; if (getOperationPlan()->getOperation() == OperationSetup::setupoperation) { for (TimeLine<LoadPlan>::const_iterator i = getResource()->getLoadPlans().begin(isStart() ? getOtherLoadPlan() : this); i != getResource()->getLoadPlans().end(); --i) { const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i); if (l && l->getOperationPlan() != getOperationPlan() && l->getOperationPlan() != getOperationPlan()->getOwner() && !l->isStart()) { prevldplan = const_cast<LoadPlan*>(l); break; } } if (!prevldplan) { for (TimeLine<LoadPlan>::const_iterator i = getResource()->getLoadPlans().begin(isStart() ? getOtherLoadPlan() : this); i != getResource()->getLoadPlans().end(); ++i) { const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i); if (l && l->getOperationPlan() != getOperationPlan() && l->getOperationPlan() != getOperationPlan()->getOwner() && !l->isStart()) { prevldplan = const_cast<LoadPlan*>(l); break; } } } } // Change this loadplan and its brother for (LoadPlan *ldplan = getOtherLoadPlan(); true; ) { // Remove from the old resource, if there is one if (res) { res->loadplans.erase(ldplan); res->setChanged(); } // Insert in the new resource. // This code assumes the date and quantity of the loadplan don't change // when a new resource is assigned. ldplan->res = newres; newres->loadplans.insert( ldplan, ld->getLoadplanQuantity(ldplan), ld->getLoadplanDate(ldplan) ); // Repeat for the brother loadplan or exit if (ldplan != this) ldplan = this; else break; } // Update the setups on the old resource if (prevldplan) prevldplan->res->updateSetups(prevldplan); // Change the resource newres->setChanged(); }
extern "C" PyObject* OperationItemSupplier::createOrder( PyObject *self, PyObject *args, PyObject *kwdict ) { // Parse the Python arguments PyObject* pylocation = NULL; unsigned long id = 0; const char* ref = NULL; PyObject* pyitem = NULL; PyObject* pysupplier = NULL; double qty = 0; PyObject* pystart = NULL; PyObject* pyend = NULL; const char* status = NULL; const char* source = NULL; static const char *kwlist[] = { "location", "id", "reference", "item", "supplier", "quantity", "start", "end", "status", "source", NULL }; int ok = PyArg_ParseTupleAndKeywords( args, kwdict, "|OkzOOdOOzz:createOrder", const_cast<char**>(kwlist), &pylocation, &id, &ref, &pyitem, &pysupplier, &qty, &pystart, &pyend, &status, &source ); if (!ok) return NULL; Date start = pystart ? PythonData(pystart).getDate() : Date::infinitePast; Date end = pyend ? PythonData(pyend).getDate() : Date::infinitePast; // Validate all arguments if (!pylocation || !pyitem) { PyErr_SetString(PythonDataException, "item and location arguments are mandatory"); return NULL; } PythonData location_tmp(pylocation); if (!location_tmp.check(Location::metadata)) { PyErr_SetString(PythonDataException, "location argument must be of type location"); return NULL; } PythonData item_tmp(pyitem); if (!item_tmp.check(Item::metadata)) { PyErr_SetString(PythonDataException, "item argument must be of type item"); return NULL; } PythonData supplier_tmp(pysupplier); if (pysupplier && !supplier_tmp.check(Supplier::metadata)) { PyErr_SetString(PythonDataException, "supplier argument must be of type supplier"); return NULL; } Item *item = static_cast<Item*>(item_tmp.getObject()); Location *location = static_cast<Location*>(location_tmp.getObject()); Supplier *supplier = pysupplier ? static_cast<Supplier*>(supplier_tmp.getObject()) : NULL; // Find or create the destination buffer. Buffer* destbuffer = NULL; for (Buffer::iterator bufiter = Buffer::begin(); bufiter != Buffer::end(); ++bufiter) { if (bufiter->getLocation() == location && bufiter->getItem() == item) { if (destbuffer) { stringstream o; o << "Multiple buffers found for item '" << item << "'' and location'" << location << "'"; throw DataException(o.str()); } destbuffer = &*bufiter; } } if (!destbuffer) { // Create the destination buffer destbuffer = new BufferDefault(); stringstream o; o << item << " @ " << location; destbuffer->setName(o.str()); destbuffer->setItem(item); destbuffer->setLocation(location); } // Look for a matching matching supplying operation on this buffer. // Here we also trigger the creation of its producing operation, which // contains the logic to build possible transfer operations. Operation *oper = NULL; Operation* prodOper = destbuffer->getProducingOperation(); if (prodOper && prodOper->getType() == *OperationItemSupplier::metadata) { if (supplier) { if (supplier->isMemberOf(static_cast<OperationItemSupplier*>(prodOper)->getItemSupplier()->getSupplier())) oper = prodOper; } else oper = prodOper; } else if (prodOper && prodOper->getType() == *OperationAlternate::metadata) { SubOperation::iterator soperiter = prodOper->getSubOperationIterator(); while (SubOperation *soper = soperiter.next()) { if (soper->getType() == *OperationItemSupplier::metadata) { if (supplier) { if (supplier->isMemberOf(static_cast<OperationItemSupplier*>(prodOper)->getItemSupplier()->getSupplier())) { oper = soper->getOperation(); break; } } else { oper = prodOper; break; } } } } // No matching operation is found. if (!oper) { // We'll create one now, but that requires that we have a supplier defined. if (!supplier) throw DataException("Supplier is needed on this purchase order"); // Note: We know that we need to create a new one. An existing one would // have created an operation on the buffer already. ItemSupplier *itemsupplier = new ItemSupplier(); itemsupplier->setSupplier(supplier); itemsupplier->setItem(item); itemsupplier->setLocation(location); oper = new OperationItemSupplier(itemsupplier, destbuffer); new ProblemInvalidData(oper, "Purchase orders on unauthorized supplier", "operation", Date::infinitePast, Date::infiniteFuture, 1); } // Finally, create the operationplan OperationPlan *opplan = oper->createOperationPlan(qty, start, end); if (status) opplan->setStatus(status); if (ref) opplan->setReference(ref); // Return result Py_INCREF(opplan); return opplan; }
extern "C" PyObject* OperationItemDistribution::createOrder( PyObject *self, PyObject *args, PyObject *kwdict ) { // Parse the Python arguments PyObject* pydest = NULL; unsigned long id = 0; const char* ref = NULL; PyObject* pyitem = NULL; PyObject* pyorigin = NULL; double qty = 0; PyObject* pystart = NULL; PyObject* pyend = NULL; int consume = 1; const char* status = NULL; const char* source = NULL; static const char *kwlist[] = { "destination", "id", "reference", "item", "origin", "quantity", "start", "end", "consume_material", "status", "source", NULL }; int ok = PyArg_ParseTupleAndKeywords( args, kwdict, "|OkzOOdOOpzz:createOrder", const_cast<char**>(kwlist), &pydest, &id, &ref, &pyitem, &pyorigin, &qty, &pystart, &pyend, &consume, &status, &source ); if (!ok) return NULL; Date start = pystart ? PythonData(pystart).getDate() : Date::infinitePast; Date end = pyend ? PythonData(pyend).getDate() : Date::infinitePast; // Validate all arguments if (!pydest || !pyitem) { PyErr_SetString(PythonDataException, "item and destination arguments are mandatory"); return NULL; } PythonData dest_tmp(pydest); if (!dest_tmp.check(Location::metadata)) { PyErr_SetString(PythonDataException, "destination argument must be of type location"); return NULL; } PythonData item_tmp(pyitem); if (!item_tmp.check(Item::metadata)) { PyErr_SetString(PythonDataException, "item argument must be of type item"); return NULL; } PythonData origin_tmp(pyorigin); if (pyorigin && !origin_tmp.check(Location::metadata)) { PyErr_SetString(PythonDataException, "origin argument must be of type location"); return NULL; } Item *item = static_cast<Item*>(item_tmp.getObject()); Location *dest = static_cast<Location*>(dest_tmp.getObject()); Location *origin = pyorigin ? static_cast<Location*>(origin_tmp.getObject()) : NULL; // Find or create the destination buffer. Buffer* destbuffer = NULL; for (Buffer::iterator bufiter = Buffer::begin(); bufiter != Buffer::end(); ++bufiter) { if (bufiter->getLocation() == dest && bufiter->getItem() == item) { if (destbuffer) { stringstream o; o << "Multiple buffers found for item '" << item << "'' and location'" << dest << "'"; throw DataException(o.str()); } destbuffer = &*bufiter; } } if (!destbuffer) { // Create the destination buffer destbuffer = new BufferDefault(); stringstream o; o << item << " @ " << dest; destbuffer->setName(o.str()); destbuffer->setItem(item); destbuffer->setLocation(dest); } // Build the producing operation for this buffer. destbuffer->getProducingOperation(); // Look for a matching operation replenishing this buffer. Operation *oper = NULL; for (Buffer::flowlist::const_iterator flowiter = destbuffer->getFlows().begin(); flowiter != destbuffer->getFlows().end() && !oper; ++flowiter) { if (flowiter->getOperation()->getType() != *OperationItemDistribution::metadata || flowiter->getQuantity() <= 0) continue; OperationItemDistribution* opitemdist = static_cast<OperationItemDistribution*>(flowiter->getOperation()); if (origin) { // Origin must match as well for (Operation::flowlist::const_iterator fl = opitemdist->getFlows().begin(); fl != opitemdist->getFlows().end(); ++ fl) { if (fl->getQuantity() < 0 && fl->getBuffer()->getLocation()->isMemberOf(origin)) oper = opitemdist; } } else oper = opitemdist; } // No matching operation is found. if (!oper) { // We'll create one now, but that requires that we have an origin defined. if (!origin) throw DataException("Origin location is needed on this distribution order"); Buffer* originbuffer = NULL; for (Buffer::iterator bufiter = Buffer::begin(); bufiter != Buffer::end(); ++bufiter) { if (bufiter->getLocation() == origin && bufiter->getItem() == item) { if (originbuffer) { stringstream o; o << "Multiple buffers found for item '" << item << "'' and location'" << dest << "'"; throw DataException(o.str()); } originbuffer = &*bufiter; } } if (!originbuffer) { // Create the origin buffer originbuffer = new BufferDefault(); stringstream o; o << item << " @ " << origin; originbuffer->setName(o.str()); originbuffer->setItem(item); originbuffer->setLocation(origin); } // Note: We know that we need to create a new one. An existing one would // have created an operation on the buffer already. ItemDistribution *itemdist = new ItemDistribution(); itemdist->setOrigin(origin); itemdist->setItem(item); itemdist->setDestination(dest); oper = new OperationItemDistribution(itemdist, originbuffer, destbuffer); new ProblemInvalidData(oper, "Distribution orders on unauthorized lanes", "operation", Date::infinitePast, Date::infiniteFuture, 1); } // Finally, create the operationplan OperationPlan *opplan = oper->createOperationPlan(qty, start, end, NULL, NULL, 0, false); if (id) opplan->setIdentifier(id); if (status) opplan->setStatus(status); if (ref) opplan->setReference(ref); if (!consume) opplan->setConsumeMaterial(false); opplan->activate(); // Return result Py_INCREF(opplan); return opplan; }
bool OperationPlan::updateFeasible() { if (!getOperation()->getDetectProblems()) { // No problems to be flagged on this operation setFeasible(true); return true; } // The implementation of this method isn't really cleanly object oriented. It uses // logic which only the different resource and buffer implementation classes should be // aware. if (firstsubopplan) { // Check feasibility of child operationplans for (OperationPlan *i = firstsubopplan; i; i = i->nextsubopplan) { if (!i->updateFeasible()) { setFeasible(false); return false; } } } else { // Before current and before fence problems are only detected on child operationplans if (getConfirmed()) { if (dates.getEnd() < Plan::instance().getCurrent()) { // Before current violation setFeasible(false); return false; } } else { if (dates.getStart() < Plan::instance().getCurrent()) { // Before current violation setFeasible(false); return false; } else if (dates.getStart() < Plan::instance().getCurrent() + oper->getFence() && getProposed()) { // Before fence violation setFeasible(false); return false; } } } if (nextsubopplan && getEnd() > nextsubopplan->getStart() + Duration(1L) && !nextsubopplan->getConfirmed() && owner && !owner->getOperation()->hasType<OperationSplit>() ) { // Precedence violation // Note: 1 second grace period for precedence problems to avoid rounding issues setFeasible(false); return false; } // Verify the capacity constraints for (auto ldplan = getLoadPlans(); ldplan != endLoadPlans(); ++ldplan) { if (ldplan->getResource()->hasType<ResourceDefault>() && ldplan->getQuantity() > 0) { auto curMax = ldplan->getMax(); for ( auto cur = ldplan->getResource()->getLoadPlans().begin(&*ldplan); cur != ldplan->getResource()->getLoadPlans().end(); ++cur ) { if (cur->getOperationPlan() == this && cur->getQuantity() < 0) break; if (cur->getEventType() == 4) curMax = cur->getMax(false); if ( cur->getEventType() != 5 && cur->isLastOnDate() && cur->getOnhand() > curMax + ROUNDING_ERROR ) { // Overload on default resource setFeasible(false); return false; } } } else if (ldplan->getResource()->hasType<ResourceBuckets>()) { for ( auto cur = ldplan->getResource()->getLoadPlans().begin(&*ldplan); cur != ldplan->getResource()->getLoadPlans().end() && cur->getEventType() != 2; ++cur ) { if (cur->getOnhand() < -ROUNDING_ERROR) { // Overloaded capacity on bucketized resource setFeasible(false); return false; } } } } // Verify the material constraints for (auto flplan = beginFlowPlans(); flplan != endFlowPlans(); ++flplan) { if ( !flplan->getFlow()->isConsumer() || flplan->getBuffer()->hasType<BufferInfinite>() ) continue; auto flplaniter = flplan->getBuffer()->getFlowPlans(); for (auto cur = flplaniter.begin(&*flplan); cur != flplaniter.end(); ++cur) { if (cur->getOnhand() < -ROUNDING_ERROR && cur->isLastOnDate()) { // Material shortage setFeasible(false); return false; } } } // After all checks, it turns out to be feasible setFeasible(true); return true; }