Exemple #1
0
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()
Exemple #2
0
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