def kickoff(cls, user, materials=None): """ Create new MRP session, extracting existing data :param materials - list of tuple [(stock_code, revision, size)] :return: MRPSession Object """ # Check is_running if cls.running_session() is not None: raise err.ProhibitedError('There is MRP Session running.') if not isinstance(materials, (type(None), list)): raise err.ProhibitedError( 'Bad value - materials must be list of tuple size of 3') session = MRPSession() session.target_materials = materials session.issued_by = user key = str(session.object_id) LOG.debug("Running MRP Session=%s" % key) t = multiprocessing.Process(target=MRPSession.run, args=(session, )) cls.processes[key] = t t.start() return session
def _group_id(dt): if lz == MaterialMaster.LZ_DAILY: return dt.date() elif lz == MaterialMaster.LZ_MONTHLY: return "%04d%02d" % (dt.year, dt.month) elif lz == MaterialMaster.LZ_WEEKLY: return "%04d%02d" % (dt.year, dt.date().isocalendar()[1]) else: raise err.ProhibitedError('Invalid lot_size value=%s' % lot_size)
def populate(self, path): if path == 'material_master' and self.material_master is None and self.material is not None: key = str(self.material) mms = MaterialMaster.manager.find(cond={'code': key}, pagesize=1) if len(mms) == 0: raise err.ProhibitedError('Material Master %s is missing' % key) self.material_master = mms[0] # type: MaterialMaster self.mrp_type = self.material_master.mrp_type self.reorder_point = 0 if self.material_master == MaterialMaster.MRP else self.material_master.reorder_point self.lead_time = self.material_master.gr_processing_time self.procurement_type = self.material_master.procurement_type else: super(MRPSessionExecutionRecord, self).populate(path) return self
def create_demand_groups(self): lot_size = self.material_master.lot_size if lot_size in [ MaterialMaster.LZ_LOT_FOR_LOT, MaterialMaster.LZ_MAX_STOCK_LEVEL ]: def lot_for_lot(): r = [] for a in self.entries: if a.marker.date() in [ MRPSessionExecutionRecordEntry. initial_balance_marker(), MRPSessionExecutionRecordEntry.safety_stock_marker( ) ]: r.append(a) else: if len(r) > 0: yield None, r r = [] yield a.marker, [a] return lot_for_lot() elif lot_size in [ MaterialMaster.LZ_DAILY, MaterialMaster.LZ_MONTHLY, MaterialMaster.LZ_WEEKLY ]: def create_timed_lot_size(lz): def _group_id(dt): if lz == MaterialMaster.LZ_DAILY: return dt.date() elif lz == MaterialMaster.LZ_MONTHLY: return "%04d%02d" % (dt.year, dt.month) elif lz == MaterialMaster.LZ_WEEKLY: return "%04d%02d" % (dt.year, dt.date().isocalendar()[1]) else: raise err.ProhibitedError('Invalid lot_size value=%s' % lot_size) def timed_lot_size(): group_point = None r = [] for e in self.entries: if group_point is None: # do not yield the automatic ones group_point = _group_id(e.marker) r.append(e) elif group_point != _group_id(e.marker): yield group_point, r # renew group_point = _group_id(e.marker) r = [e] else: r.append(e) if len(r) > 0: yield group_point, r return timed_lot_size return create_timed_lot_size(lot_size)() raise err.ProhibitedError('Unknown lot_size %s' % lot_size)
def run(self): self._begin() try: def report(message, group=None, level=0): if group is None: LOG.debug("%s%s" % ("\t" * level, message)) else: LOG.debug("%s%s: %s" % ("\t" * level, group, message)) report("\nBuilding Run Sequences") self._build_run_sequences() report("\nInitial sequence" % self.sequence) if len(self.sequence) == 0: report("(No sequence initialized)") else: for s in self.sequence: report(s, "i", 1) report("") # Run sequence; Execute mrp for seq in self.sequence: # Read Material # => update reorder_point, lead_time, lot_size -> making 'create_supply()' seq.populate('material_master') # Extract attributes reorder_point = seq.reorder_point procurement_type = seq.material_master.procurement_type lot_size = seq.material_master.lot_size mrp_type = seq.material_master.mrp_type report("Started %s" % seq) report("reorder_point=%s" % reorder_point, "i", 1) report("lot_size=%s" % lot_size, "i", 1) report( "supply_type=%s" % ('PurchaseRequisition' if procurement_type == MaterialMaster.EXTERNAL else 'ProductionOrder'), 'i', 1) # Type = 'MRP' or 'Reorder', if 'No MRP' = Skip if mrp_type not in [ MaterialMaster.MRP, MaterialMaster.REORDER ]: raise err.ProhibitedError( 'Unable to process MRP Sequence %s - incorrect MRP type' % seq.material) # Delete weak supply seq.delete_weak_supplies(verbose=report) # Gathering Demand/Supply seq.gather(verbose=report) report("-", "i", 1) # Go through Demand/Supply in chronological order # Try to resolve negative balance situation. current = 0 for group_id, a in seq.create_demand_groups(): first_marker = a[0].marker # print demand report("marker=%s group_id=%s" % (first_marker, group_id), "O", 1) # e = MRPSessionExecutionRecordEntry for e in a: current += e.quantity report("%s\t= %s" % (e, current), level=2) # For Every demand group, we need to produce a supply for it. # TODO: Each demand group might have a supply within that will in turn misled the calculation. if current <= seq.reorder_point: reorder_amount = current - seq.reorder_point # MAX_STOCK_LEVEL if lot_size == MaterialMaster.LZ_MAX_STOCK_LEVEL: max_stock_level = seq.material_master.lot_size_arg if max_stock_level <= 0 or max_stock_level is None: raise err.BadParameterError( 'Invalid lot_size_arg for material %s' % seq.material) reorder_amount = current - max_stock_level # IF supply is needed if reorder_amount < 0: replenished = seq.create_supply(-reorder_amount, first_marker, self, ref_doc=e.ref_docs, remark=e.remark, verbose=report) current += replenished # create sequence handle this replenishing # just for printing sake rec = MRPSessionExecutionRecordEntry.create( marker=first_marker, quantity=replenished, ref_docs=e.ref_docs, remark=e.remark, original=False) seq.created_supplies.append(rec) report("%s\t= %s %s" % (rec, current, rec.remark), level=2) else: report("no shortage found", "i", 1) report("DONE\n", "i", level=1) self._enclose() except Exception as e: LOG.error(traceback.format_exc()) self._enclose(False, [str(e)])
def create_supply(self, shortage, due, session, ref_doc=None, remark=None, verbose=None): """ Modify add supply to sequence, and return replenished amount Replenished amount is ... shortage + lot_size_arg if lot_size = MaterialMaster.LZ_MAX_STOCK_LEVEL shortage if otherwise Supply Sequence can be broken into lots using lot_size_max Each sub_lot_size must be greater or equals to lot_size_min lead_time is taken into consideration as a due due is given as exact moment when it will be used, therefore it will be offset by 1 hours before such exact hours. (Please, Consider exclude OffHours as optional feature) :param int shortage: :param due: :param MRPSession session: :param ref_doc: :param basestring remark: :param verbose: :return: (amount replenished) """ if shortage == 0: return 0 if self.lead_time is None or self.reorder_point is None: raise err.ProhibitedError('Cannot create supply without lead_time') offset_due = due - timedelta(hours=1, days=self.lead_time) # Compute optimal lot_size procurement_type = self.material_master.procurement_type lots = [] lot_size_max = self.material_master.lot_size_max lot_size_min = self.material_master.lot_size_min # lot_size_arg = self.material_master.lot_size_arg # Capped with lot_size_max if lot_size_max is not None and lot_size_max > 0: lot_count = int(shortage) / int(lot_size_max) supplied = 0 for i in xrange(0, lot_count - 1): lots.append(lot_size_max) supplied += lot_size_max lots.append(shortage - supplied) else: lots = [shortage] # Push to lot_size_min if lot_size_min is not None and lot_size_min > 0: lots = map(lambda a: max(lot_size_min, a), lots) # Actually create supply references = [] if procurement_type == MaterialMaster.INTERNAL: # ProductionOrder # Need to honour scrap percentage here scrap_percentage = self.material_master.scrap_percentage references.extend( map( lambda a: ProductionOrder.factory( self.material, self.revision, self.size, offset_due, a, session.issued_by, ref_doc=ref_doc, remark=remark, scrap_percentage=scrap_percentage, mrp_session_id=session.object_id), lots)) elif procurement_type == MaterialMaster.EXTERNAL: # PurchaseRequisition # FIXME: Consider adding remark/ref_doc to the output document. references.extend( map( lambda a: PurchaseRequisition.factory( self.material, self.revision, self.size, offset_due, a, session.issued_by, mrp_session_id=session.object_id), lots)) verbose("due=%s lots=%s remark=%s" % (due, lots, remark), "S", 1) return reduce(lambda o, a: o + a, lots, 0)