DECLARE_EXPORT void Flow::validate(Action action) { // Catch null operation and buffer pointers Operation* oper = getOperation(); Buffer* buf = getBuffer(); if (!oper || !buf) { // This flow is not a valid one since it misses essential information if (!oper && !buf) throw DataException("Missing operation and buffer on a flow"); else if (!oper) throw DataException("Missing operation on a flow with buffer '" + buf->getName() + "'"); else throw DataException("Missing buffer on a flow with operation '" + oper->getName() + "'"); } // Check if a flow with 1) identical buffer, 2) identical operation and // 3) overlapping effectivity dates already exists, and 4) same // flow type. Operation::flowlist::const_iterator i = oper->getFlows().begin(); for (; i != oper->getFlows().end(); ++i) if (i->getBuffer() == buf && i->getEffective().overlap(getEffective()) && i->getType() == getType() && &*i != this) break; // Apply the appropriate action switch (action) { case ADD: if (i != oper->getFlows().end()) throw DataException("Flow of '" + oper->getName() + "' and '" + buf->getName() + "' already exists"); break; case CHANGE: throw DataException("Can't update a flow"); case ADD_CHANGE: // ADD is handled in the code after the switch statement if (i == oper->getFlows().end()) break; throw DataException("Can't update a flow between '" + oper->getName() + "' and '" + buf->getName() + "')"); case REMOVE: // Delete the temporary flow object delete this; // Nothing to delete if (i == oper->getFlows().end()) throw DataException("Can't remove nonexistent flow of '" + oper->getName() + "' and '" + buf->getName() + "'"); // Delete delete &*i; } // Set a flag to make sure the level computation is triggered again HasLevel::triggerLazyRecomputation(); }
Object* Flow::finder(const DataValueDict& d) { // Check operation const DataValue* tmp = d.get(Tags::operation); if (!tmp) return nullptr; Operation* oper = static_cast<Operation*>(tmp->getObject()); // Check buffer field tmp = d.get(Tags::buffer); if (!tmp) return nullptr; Buffer* buf = static_cast<Buffer*>(tmp->getObject()); // Walk over all flows of the operation, and return // the first one with matching const DataValue* hasEffectiveStart = d.get(Tags::effective_start); Date effective_start; if (hasEffectiveStart) effective_start = hasEffectiveStart->getDate(); const DataValue* hasEffectiveEnd = d.get(Tags::effective_end); Date effective_end; if (hasEffectiveEnd) effective_end = hasEffectiveEnd->getDate(); const DataValue* hasPriority = d.get(Tags::priority); int priority = 1; if (hasPriority) priority = hasPriority->getInt(); const DataValue* hasName = d.get(Tags::name); string name; if (hasName) name = hasName->getString(); for (Operation::flowlist::const_iterator fl = oper->getFlows().begin(); fl != oper->getFlows().end(); ++fl) { if (fl->getBuffer() != buf) continue; if (hasEffectiveStart && fl->getEffectiveStart() != effective_start) continue; if (hasEffectiveEnd && fl->getEffectiveEnd() != effective_end) continue; if (hasPriority && fl->getPriority() != priority) continue; if (hasName && fl->getName() != name) continue; return const_cast<Flow*>(&*fl); } return nullptr; }
void OperationItemSupplier::trimExcess(bool zero_or_minimum) const { // This method can only trim operations not loading a resource if (getLoads().begin() != getLoads().end()) return; for (Operation::flowlist::const_iterator fliter = getFlows().begin(); fliter != getFlows().end(); ++fliter) { if (fliter->getQuantity() <= 0) // Strange, shouldn't really happen continue; FlowPlan* candidate = nullptr; double curmin = 0; double oh = 0; double excess_min = DBL_MAX; for (Buffer::flowplanlist::const_iterator flplniter = fliter->getBuffer()->getFlowPlans().begin(); flplniter != fliter->getBuffer()->getFlowPlans().end(); ++flplniter) { // For any operationplan we get the onhand when its successor // replenishment arrives. If that onhand is higher than the minimum // onhand value we can resize it. // This is only valid in unconstrained plans and when there are // no upstream activities. if (flplniter->getEventType() == 3 && zero_or_minimum) curmin = flplniter->getMin(); else if (flplniter->getEventType() == 1) { const FlowPlan* flpln = static_cast<const FlowPlan*>(&*flplniter); if (oh - curmin < excess_min) { excess_min = oh - curmin; if (excess_min < 0) excess_min = 0; } if (flpln->getQuantity() > 0 && !flpln->getOperationPlan()->getLocked() && (!candidate || candidate->getDate() != flpln->getDate())) { if (candidate && excess_min > ROUNDING_ERROR && candidate->getQuantity() > excess_min + ROUNDING_ERROR && candidate->getQuantity() > getSizeMinimum() + ROUNDING_ERROR ) { // This candidate can now be resized candidate->setQuantity(candidate->getQuantity() - excess_min, false); candidate = nullptr; } else if (flpln->getOperation() == this) candidate = const_cast<FlowPlan*>(flpln); else candidate = nullptr; excess_min = DBL_MAX; } } oh = flplniter->getOnhand(); } if (candidate && excess_min > ROUNDING_ERROR && candidate->getQuantity() > excess_min + ROUNDING_ERROR && candidate->getQuantity() > getSizeMinimum() + ROUNDING_ERROR ) // Resize the last candidate at the end of the horizon candidate->setQuantity(candidate->getQuantity() - excess_min, false); } }
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; }