class TaskComponent(doc.FieldSpecAware, decorators.JsonSerializable): # assign from front end material = doc.FieldTypedCode(codes.StockCode) # TypedCode quantity = doc.FieldNumeric(default=1, none=False) uom = doc.FieldUom(none=False) revision = doc.FieldNumeric(none=True) size = doc.FieldString(none=True) @classmethod def factory(cls, material, revision, size, quantity, uom=None, **kwargs): c = cls() c.material = material c.revision = revision c.size = size c.quantity = quantity c.uom = uom if uom is not None else MaterialMaster.get(material).uom return c def as_json(self): return { 'material': str(self.material), 'quantity': self.quantity, 'uom': self.uom, 'revision': self.revision, 'size': self.size } def material_key(self): return "%sr%s-%s" % (self.material, self.revision, self.size) def __repr__(self): return "TaskComponent material_key=[%s]" % self.material_key()
class PurchaseBaseItem(doc.FieldSpecAware): material = doc.FieldTypedCode(codes.StockCode, none=False) uom = doc.FieldUom(none=False, default='pc') quantity = doc.FieldNumeric(default=1) revision = doc.FieldNumeric(none=True) size = doc.FieldString(none=True) location = doc.FieldString(default=Location.locations['STORE'].code, none=False) delivery_date = doc.FieldDateTime() net_price = doc.FieldNumeric()
class SalesOrderEntry(doc.Doc): material = doc.FieldTypedCode(codes.StockCode, none=False) revision = doc.FieldNumeric(none=True) quantity = doc.FieldNumeric(default=1, none=False) uom = doc.FieldUom(none=False) location = doc.FieldString(default=Location.locations['STORE'].code, none=False) size = doc.FieldString(none=True) # TODO: weight? net_price = doc.FieldNumeric(none=False, default=0) remark = doc.FieldString(none=True)
class SchematicMaterial(doc.FieldSpecAware): """ SchematicMaterial = Schematic's component An entry that represent as a material to be used within SchematicEntry """ code = doc.FieldTypedCode(codes.StockCode, allow_incomplete=True) # TypedCode quantity = doc.FieldList(doc.FieldNumeric(default=1, none=False)) is_configurable = doc.FieldBoolean(default=False) counter = doc.FieldUom( none=True) # default from material object resolved from code cost = doc.FieldNumeric( default=0, none=False) # default from material object resolved from code def normalized(self, for_size_index): o = self.__class__() # easy stuff o.code = self.code o.is_configurable = False o.counter = self.counter o.cost = self.cost # size sensitive stuff if not self.is_configurable: o.quantity = self.quantity[:] else: if len(self.quantity) <= for_size_index or for_size_index < 0: raise ValidationError( _("ERR_NORMALIZE_SCHEMATIC_ENTRY_FAILED_MATERIAL_QUANTITY_SIZE_INDEX_OUT_OF_RANGE" )) o.quantity = [self.quantity[for_size_index]] return o def __repr__(self): return "%s x %s %s %s@%s" % (self.code, self.quantity, str( self.counter), "(conf) " if self.is_configurable else "", self.cost)
class MaterialMaster(doc.Authored): MRP = 'M' NO_MRP = 'N' REORDER = 'R' MRP_TYPES = ( (MRP, _("MRP_TYPE_MRP")), (NO_MRP, _("MRP_TYPE_NO_MRP")), (REORDER, _("MRP_TYPE_REORDER")), ) EXTERNAL = 'E' INTERNAL = 'I' PROCUREMENT_TYPES = ((EXTERNAL, _("PROCUREMENT_TYPE_EXTERNAL")), (INTERNAL, _("PROCUREMENT_TYPE_INTERNAL"))) # Lot Size Policy LZ_LOT_FOR_LOT = 'EX' # One procurement for One demand LZ_DAILY = 'TB' # Accumulate daily demand to one procurement amount LZ_WEEKLY = 'WS' # Accumulate weekly demand to one procurement amount for the whole week based on offset (:lot_size_arg value 0 <= 6) LZ_MONTHLY = 'MS' # Accumulate monthly demand to one procurement amount for the whole month based on offset (:lot_size_arg value 0 <= 28) LZ_MAX_STOCK_LEVEL = 'HB' # maximize amount to the :lot_size_arg, at the multiplication of :lot_size_max, :lot_size_min LOT_SIZES = ( (LZ_LOT_FOR_LOT, _("LOT_SIZE_LOT_FOR_LOT")), (LZ_DAILY, _("LOT_SIZE_DAILY")), (LZ_WEEKLY, _("LOT_SIZE_WEEKLY")), (LZ_MONTHLY, _("LOT_SIZE_MONTHLY")), (LZ_MAX_STOCK_LEVEL, _("LOT_SIZE_MAX_STOCK_LEVEL")), ) AI_A = 'A' AI_B = 'B' AI_C = 'C' AI_D = 'D' AI_E = 'E' AI_INDICATORS = ( (AI_A, "A"), (AI_B, "B"), (AI_C, "C"), (AI_D, "D"), (AI_E, "E"), ) PLANT_AVAILABLE = 'AV' PLANT_BLOCKED = 'BL' PLANT_STATUSES = ((PLANT_AVAILABLE, _("PLANT_STATUS_AVAILABLE")), (PLANT_BLOCKED, _("PLANT_STATUS_BLOCKED"))) code = doc.FieldTypedCode(codes.StockCode, none=False) # General Info uom = doc.FieldUom(none=False) description = doc.FieldString(max_length=500, none=True) gross_weight = doc.FieldNumeric(none=True) net_weight = doc.FieldNumeric(none=True) plant_status = doc.FieldString(choices=PLANT_STATUSES, default=PLANT_AVAILABLE) # MRP scrap_percentage = doc.FieldNumeric(none=False, default=0) # type: float scale = doc.FieldNumeric(none=False, default=1) # type: float mrp_type = doc.FieldString(choices=MRP_TYPES, default=MRP, max_length=1) location = doc.FieldString(default=Location.locations['STORE'].code) reorder_point = doc.FieldNumeric(none=False, default=0) planning_time_fence = doc.FieldNumeric(default=0) procurement_type = doc.FieldString( choices=PROCUREMENT_TYPES, default=EXTERNAL, none=False) # Default logic is based on stock code gr_processing_time = doc.FieldNumeric(default=0, validators=[(lambda v: v < 0, 'Positive value only')]) planned_delivery_time = doc.FieldNumeric(default=2) lot_size = doc.FieldString(choices=LOT_SIZES, default=LZ_LOT_FOR_LOT) lot_size_arg = doc.FieldNumeric(none=True) # depends on lot_size lot_size_min = doc.FieldNumeric(default=0) lot_size_max = doc.FieldNumeric(default=0) safety_stock = doc.FieldNumeric( default=0, none=True) # if value is None then calculate it instead. deprecated_date = doc.FieldDateTime(none=True, default=None) deprecated_replacement = doc.FieldDoc('material_master', none=True, default=None) # Purchasing ... # Inventory enable_cycle_counting = doc.FieldBoolean(none=False, default=True) abc_indicator = doc.FieldString(choices=AI_INDICATORS, default=AI_E, max_length=1) # MRP calculation, running priorities hierarchy_affinity = doc.FieldNumeric(none=True) # deactivated schematic = doc.FieldDoc('material_schematic', none=True) # Active schematic def get_safety_stock(self): if self.safety_stock is None: # FIXME: Calculate safety_stock based on consumption rate pass return self.safety_stock def update_schematic(self, user, schematic_object, message="update schematic"): if not isinstance(schematic_object, Schematic): raise ProhibitedError( "update_schematic only accept schematic_object") schematic_object.material_id = self self.schematic = schematic_object self.hierarchy_affinity = None # required new setup self.touched(user, message=message) def revisions(self): """ :return: all possible revisions of this material object """ return Schematic.revisions(self.object_id) def revision(self, revision_id): """ Return specific revision of given this material. :param revision_id: :return: """ if revision_id is None: return self.schematic return Schematic.of(self.object_id, revision_id, throw=False) def validate_pair(self, revision_id, size): return Schematic.pair_exists(self.object_id, revision_id, size) def has_schematic(self): if self.schematic is not None: self.populate('schematic') return len(self.schematic.schematic) > 0 return False @classmethod def get(cls, code): """ Lookup material master by code. And raise error if such code is not found. :param basestring|codes.StockCode code: :return: MaterialMaster """ mm = MaterialMaster.of('code', str(code)) if mm is None: raise BadParameterError( _('ERROR_UNKNOWN_MATERIAL %(material_code)s') % {'material_code': code}) return mm @classmethod def factory(cls, code, uom=None, procurement_type=EXTERNAL, author=None, scrap_percentage=0): """ Lookup by Code first, - if not exists, - if UoM is not supplied - raise Exception - create a MaterialMaster - if exists, - return a fetched MaterialMaster :param codes.StockCode code: :param basestring|UOM uom: :param basestring procurement_type: :param IntraUser author: :param int scrap_percentage: :return MaterialMaster: """ ProhibitedError.raise_if(not isinstance(code, codes.StockCode), "provided code must be StockCode instance") materials = cls.manager.find(1, 0, cond={'code': str(code)}) # if exists if len(materials) > 0: return materials[0] if uom is None or author is None: raise ProhibitedError( "UOM and author must be supplied in case of creation") if not UOM.has(uom): raise BadParameterError("UOM \"%s\" is invalid" % uom) # Initialize Scale according to code if re.compile('^stock-[A-Z]{3}02[123]').match(str(code)) is not None: scale = 10 else: scale = 1 o = cls() o.code = code o.uom = uom o.procurement_type = procurement_type o.scrap_percentage = scrap_percentage o.scale = scale o.touched(author) return o class Meta: collection_name = 'material_master' require_permission = True