def get_template(cls, verbose=True, **kwargs): def message(v, m): if v: print m tn = kwargs.pop('template_no', 0) raw_materials = kwargs.pop('raw_materials', cls.default_materials) raw_index = kwargs.pop('raw_index', None) mn = kwargs.pop('master_template', 0) master_model = kwargs.pop('master_model', cls.master_model) include_master = kwargs.pop('include_master', False) template = copy.deepcopy(cls.templates[tn]) template.update(cls.base_template) if include_master: template['process'].update( copy.deepcopy(cls.master_model_template[mn])) template['design_number'] = kwargs.pop('design_number', None) if not template['design_number']: raise BadParameterError("Require design number") template['code'] = kwargs.pop('code', None) if not template['code']: raise BadParameterError("Require finished material code") template['rev_unique_id'] = kwargs.pop('rev_unique_id', None) if not template['rev_unique_id']: raise BadParameterError("Require rev_unique_id") template['plating'] = [kwargs.pop('plating', "plating-18")] template['finish'] = [kwargs.pop('finish', "finish-HP")] template['style'] = [kwargs.pop('style', "style-NL")] template['stone'] = [kwargs.pop('stone', "stone-2")] template['metal'] = [kwargs.pop('metal', "metal-SILV")] template['misc'] = kwargs.pop('misc', "Mock Data") template['sales_manifest']['due_date'] = time.mktime( kwargs.pop('due_date', datetime.today().timetuple())) index = kwargs.pop('index', 0) raw_len = len(raw_materials) for k, v in template['process'].iteritems(): for process in v: for mat in process['materials']: if mat['code'] == "raw_material": ind = index % raw_len mat['code'] = raw_materials[ind][ 0] if not raw_index else raw_materials[ raw_index[ind]][0] mat['counter'] = raw_materials[ind][ 1] if not raw_index else raw_materials[ raw_index[ind]][1] index += 1 if mat['code'] == "master_model": mat['code'] = master_model[0] mat['counter'] = master_model[1] return template
def material_staging_form(request): tasks = filter(lambda a: a, request.POST.get('tasks', '').split(',')) o = prod_doc.MaterialStagingForm.factory(tasks) if o is None: raise BadParameterError(_("ERR_FAILED_TO_IDENTIFY_AUXILIARY_TASKS")) o.touched(request.user) return o.object_id
def confirm_store_aux_task(request, id): """ StoreAuxTask does NOT associate itself with UserActiveTask when it started. Therefore we can just confirm it with synthetic actual_data (NOW(), NOW(), 0) :param request: :param id: :return: """ # extract incoming materials materials = JSONDecoder().decode(request.POST.get('materials')) # lookup for aux_task first try: aux_task = task_doc.StoreAuxTask(id) except ValueError: raise BadParameterError(_("ERR_INVALID_STORE_AUX_TASK: %(id)s") % {'id': id}) # Build confirmation confirm = task_doc.AuxiliaryTaskConfirmation.factory(NOW(), NOW(), 0, request.user) # Extract materials for confirmation def convert_materials(material_dict): return task_doc.TaskComponent.factory(material_dict['material'], material_dict['revision'], material_dict['size'], material_dict['quantity']) confirm.materials = map(convert_materials, materials) # Submit to confirm aux_task.confirm(request.user, confirm) return True
def create_material_requisition(cls, user, doc_no_or_cost_center, material_list): """ :param basestring doc_no_or_cost_center: :param [dict] material_list: :return: """ # TODO: Add CostCenter Support if isinstance(doc_no_or_cost_center, Doc): doc = doc_no_or_cost_center else: doc = Docs.of_doc_no(doc_no_or_cost_center) # Check cases for ProductionOrderOperation if isinstance(doc, prod_doc.ProductionOrderOperation): # Validate ProductionOrderOperation status if not prod_doc.ProductionOrderOperation.STATUS_RELEASED <= doc.status <= prod_doc.ProductionOrderOperation.STATUS_PARTIAL_CONFIRMED: raise BadParameterError(_("ERR_INVALID_PRODUCTION_ORDER_OPERATION_STATUS")) def patch_weight(a): a['weight'] = 0 return a # Call Bank's method # prepare material_list task_signals.task_repeat.send(cls, parent=doc, components=map(patch_weight, material_list))
def factory(cls, parent_task, components=None): target_task, duration, assignee = cls.default_parameters(parent_task) if target_task is None: return None if components: if not isinstance(components, list) or not reduce( lambda x, y: isinstance(x, dict) and isinstance(y, dict), components): raise BadParameterError("Component should be list of dict") components = map( lambda a: AuxiliaryTaskComponent.factory( material=a['material'], revision=a['revision'], size=a['size'], quantity=a['quantity'], uom=a['uom'], weight=a['weight']), components) else: components = filter(lambda a: a.quantity > 0, parent_task.materials[:]) components = map(AuxiliaryTaskComponent.transmute, components) t = cls() t.status = cls.STATUS_OPEN t.task = target_task t.parent_task = parent_task t.planned_duration = duration t.assignee = assignee t.materials = components if len(t.materials) <= 0: return None return t
def translate(input, output='label', allow_incomplete=False): if output not in ['label', 'object', 'extract']: raise BadParameterError("output format '%s' is invalid" % output) # assume default type = 'stock' (type, code) = input.split('-', 1) if '-' in input else ('stock', input) c = typed_code_factory(type, code) if c is None: raise ValidationError("Unable to parse '%s'" % code) if not allow_incomplete and output not in ['extract' ] and len(c.leftover) > 0: raise ValidationError( "Unable to parse partial of code near '%s' of %s" % (c.leftover, str(c))) if 'label' == output: return c.tail().label if 'extract' == output: return c.trail return c
def task_endpoint(request, task_object_id, action): if action == "revert": task = TaskDoc.of('_id', ObjectId(task_object_id)) # Sanity check if task is None: raise BadParameterError( _("ERR_UNKNOWN_DOCUMENT_ID: %(document_id)s") % {'document_id': task_object_id}) if not isinstance(task, StoreAuxTask): raise BadParameterError( _("ERR_CANNOT_REVERT_TASK: %(task_type)s") % {'task_type': type(task)}) return task.revert(request.user) raise BadParameterError( _("ERR_INVALID_ACTION: %(action)s") % {'action': action})
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
def can(self, action_or_regex, arg=None, throw=False): """ Module's permission validation :param basestring action_or_regex: :param basestring arg: :param bool|basestring throw: either boolean or 'challenge' :return bool: :raise InsufficientPermissionError: :raise BadParameterError: """ def check(): if self.is_bot: return True # Prepare permission available to compare. permission_info = self.permissions # If we have override_permission, add it in. if self.override_permission is not None: permission_info.extend( self.override_permission.target_permissions) # Start comparing # Simple string case if isinstance(action_or_regex, basestring): action = ("%s@%s" % (action_or_regex, arg) ) if arg is not None else action_or_regex if action not in permission_center: # Check for non-existed permission will always returns True return True return action in permission_info # Regex case elif isinstance(action_or_regex, PATTERN_TYPE): return any(action_or_regex.match(m) for m in permission_info) # Failed case raise BadParameterError(_("ERR_BAD_USAGE_OF_USER_CAN_API")) if not check(): if throw == "challenge": required_permission = ( "%s@%s" % (action_or_regex, arg)) if arg is not None else action_or_regex raise InsufficientPermissionError(required_permission) elif throw: raise BadParameterError( _("ERR_INSUFFICIENT_PERMISSION: %(action)s arg=%(arg)s") % { 'action': action_or_regex, 'arg': arg }) return False return True
def transmute(cls, input_component): if not isinstance(input_component, TaskComponent): raise BadParameterError( _("ERR_CANNOT_TRANSUMTE: %(input_class)s to %(output_class)s") % { 'input_class': type(input_component), 'output_class': cls }) return cls.factory(material=input_component.material, revision=input_component.revision, size=input_component.size, quantity=input_component.quantity, uom=input_component.uom, weight=None)
def lookup(cls, design_code_with_revision, single=False): """ :param design_code_with_revision: :param single: return only one result or None :return: Array of Design object matched design_code """ if design_code_with_revision is None: raise BadParameterError("Required non-None design code string") # validate input first matches = re.compile(r'^([A-Z0-9-]+)(r(\d+))?').match(design_code_with_revision) if not matches: raise BadParameterError("Invalid design code %s" % design_code_with_revision) groups = matches.groups() design_uid = groups[0] design_rev = groups[2] design_uid = DesignUID.lookup(design_uid) if design_uid is None: raise ValidationError("Design UID %s not found." % design_uid) cond = { 'rev_unique_id': design_uid.object_id } if design_rev is not None: cond['rev_id'] = int(design_rev) r = cls.manager.find(cond=cond) if single: return r[len(r)-1] if len(r) > 0 else None else: return r
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
def confirm(self, user, confirmation, **kwargs): if not isinstance(confirmation, Confirmation): raise BadParameterError( _("ERROR_CONFIRMATION_MUST_BE_INSTANCE_OF_CONFIRMATION")) # for first confirmation, init array. otherwise, append it if len(self.confirmations) > 0: self.confirmations.append(confirmation) else: self.confirmations = [confirmation] # dispatch event signals.task_confirmed.send(self.__class__, instance=self, code=str(task.code), confirmation=confirmation) self.touched(user, **kwargs)
def return_materials(request, doc_no, type): """ Returning Materials - please see #return_candidates for more information. :param request: :param doc_no: :param type: :return: """ # Required parameters return_list = JSONDecoder().decode(request.POST.get('return_list')) if return_list is None or len(return_list) == 0: raise BadParameterError.required('return_list') return ReturningAPI.return_materials(user=request.user, doc_no=doc_no, mode=type, return_list=return_list)
def check(): if self.is_bot: return True # Prepare permission available to compare. permission_info = self.permissions # If we have override_permission, add it in. if self.override_permission is not None: permission_info.extend( self.override_permission.target_permissions) # Start comparing # Simple string case if isinstance(action_or_regex, basestring): action = ("%s@%s" % (action_or_regex, arg) ) if arg is not None else action_or_regex if action not in permission_center: # Check for non-existed permission will always returns True return True return action in permission_info # Regex case elif isinstance(action_or_regex, PATTERN_TYPE): return any(action_or_regex.match(m) for m in permission_info) # Failed case raise BadParameterError(_("ERR_BAD_USAGE_OF_USER_CAN_API"))
def activity_endpoint(request, identification, action=None): try: identification = ObjectId(identification) except: raise BadParameterError('id %s is not valid bson.ObjectId') operation_doc_id = identification # Check if operation exists try: op = TaskDoc.manager.factory('task', operation_doc_id) except ValueError: raise BadParameterError('Unknown operation %s' % operation_doc_id) if request.method == 'GET': # probe for status previous_op_count = len(op.previous_op()) if op.get_confirmable_quantity() <= 0 or ( previous_op_count == 0 and op.status != ProductionOrderOperation.STATUS_RELEASED) or ( previous_op_count > 0 and op.status != ProductionOrderOperation.STATUS_READY): return False r = UserActiveTask.probe(operation=op) return r.as_json() if r is not None else None if request.method == 'POST': # identify intention action = 'start' if action is None or action == '' else action # really perform the actions if action == 'start': # user code is required from this point user_code = request.POST.get('user_code') if not user_code: raise BadParameterError(_('ERR_USER_CODE_REQUIRED')) try: assignee = IntraUser.objects.get(code=user_code) except: raise BadParameterError('Unknown user %s' % user_code) o = UserActiveTask.start(assignee=assignee, operation=op, author=request.user) return o.as_json() # from this point we will need the probed object activity = UserActiveTask.probe(operation=op) if activity is None: raise BadParameterError(_("ERR_ACTIVITY_NOT_YET_STARTED")) if action in ['pause', 'resume']: if action == 'pause': activity.pause() if action == 'resume': activity.resume() activity.touched(request.user) return activity.as_json() if action == 'stop': # Anything that the task should required upon its confirm method. confirmation_details = JSONDecoder().decode( request.POST.get('details')) # Stop task with details activity.stop(request.user, **confirmation_details) # Delete active task UserActiveTask.manager.delete(cond={'_id': activity.object_id}) return action raise BadParameterError(_("ERR_INVALID_OPERATION"))
def return_candidates(cls, doc_no): """ Identify if given doc_no is a supported document type. Retrieve the candidate list of materials that will be returned. Process: Storage fire an API request for ``return_candidates`` Method interpret the ``doc_no`` and return the list of possible ``revert_content`` along with its interpretation types. CASE A: StoreAuxTask - with parent_task.status >= confirmed. When to use?: Task has been confirmed and there are materials left after the process. Meaning that the (parent) task has already been processed and confirmed. The returning content should be all whatever in STAGED instance. Upon return, the Complete list of items should be issued, and saved, no additional document reverted in this regard. List of: [ - type: "staged_inventory_content" - doc_no: whatever given - System gather items from ``inventory_content`` API. ] CASE B: StoreAuxTask - with parent_task.status < confirmed. When to use?: The Task has not yet been completed and is requested to return its materials (pause, or anything) Meaning that the (parent) task has not yet been started. The returning content should be those STAGED recently staged items. Upon return, ClerkAuxTask should be degraded, and so StoreAuxTask respectively. List of: [ - type: "staged_from_clerk_aux_task" - doc_no: whatever given - System gather items from ``list_components`` method and returned to Caller. ] CASE C: JOB TAG (Production Order) When to use?: Same like case B - task is requested to returns its materials. Meaning that the user wish to return some portion of associated JOB TAG. User should be presented with choices to return all their items based on their selectable tasks. List of: [ - type: "production_wip" - doc_no: <task_doc_no> - System examine the Edge Task, asking for WIP that may be created. ] :param basestring doc_no: :return: [{type, doc_no, return_list: [{TaskComponent.as_json()}]},..] """ # Retrieve documents based on what it is given if isinstance(doc_no, Doc): doc = doc_no else: doc = Docs.of_doc_no(doc_no) # Case StoreAuxTask if isinstance(doc, prod_doc.StoreAuxTask): if doc.status <= prod_doc.StoreAuxTask.STATUS_CONFIRMED: raise BadParameterError(_("ERR_INVALID_DOCUMENT_STATUS: %(doc_no)s") % { 'doc_no': doc_no }) doc.populate('parent_task') if doc.parent_task: if prod_doc.UserActiveTask.probe(operation=doc.parent_task) is not None: raise BadParameterError(_("ERR_INVALID_STATE_TASK_IN_PROGRESS: %(doc_no)s") % { 'doc_no': doc_no }) if doc.parent_task.status >= prod_doc.ProductionOrderOperation.STATUS_CONFIRMED: staged_components = stock_doc.InventoryContent.query_content(ref_doc=doc.parent_task, location=Location.factory('STAGING')) return [{ "type": cls.TYPE_STAGED_IN_INVENTORY_CONTENT, "object_id": str(doc.object_id), "doc_no": doc.doc_no, "return_list": map(lambda a: { # As TaskComponent.as_json() 'material': str(a.material), 'quantity': a.quantity, 'uom': stock_doc.MaterialMaster.get(a.material).uom.code, 'revision': None, 'weight': a.weight, 'size': None }, staged_components) }] else: staged_components = doc.list_components() return [{ "type": cls.TYPE_STAGED_FROM_CLERK_AUX_TASK, "object_id": str(doc.object_id), "doc_no": doc.doc_no, "return_list": map(lambda a: { # As TaskComponent.as_json() 'material': str(a.material), 'quantity': a.quantity, 'uom': stock_doc.MaterialMaster.get(a.material).uom.code, 'revision': None, 'weight': a.weight, 'size': None }, staged_components) }] raise BadParameterError(_("ERR_UNKNOWN_PARENT_TASK_FOR: %(doc_no)s") % { 'doc_no': doc_no }) elif isinstance(doc, prod_doc.ProductionOrder): return map(lambda a: { "type": cls.TYPE_WIP_FROM_PRODUCTION_ORDER, "object_id": str(a.object_id), "doc_no": a.doc_no, "return_list": [{ 'material': 'WIP-CANDIDATE', # WIP CANDIDATE ... 'quantity': a.get_confirmable_quantity(), 'uom': 'pc', 'revision': doc.revision, 'weight': 0, 'size': doc.size }] }, doc.get_edge_operations()) raise BadParameterError(_("ERR_UNSUPPORTED_DOCUMENT_TYPE: %(document_type)s" % { 'document_type': str(type(doc)) }))
def try_propagate(self, target_material, replacement_array): """ Replace target_material (take/borrow only) with replacement_array (array of sized materials) :param target_material: :param replacement_array: :return: """ # Sanity Check if not isinstance(target_material, codes.StockCode): raise BadParameterError.std(require_type=codes.StockCode) if not self.materials: return # all these materials can be ... # - Borrow case # - Take case <-- TODO: Should filter this case out. # - Make case append_list = [ ] # to keep the loop safe, we will extend this self.materials once we complete the removal process_list = list(reversed(list(enumerate(self.materials)))) print 'Process_list', process_list print '\tlooking for', target_material print '\tReplacement_array', replacement_array for sch_mat_index, sch_mat in process_list: if sch_mat.code == target_material: # Sanity check if sch_mat.is_configurable and len( sch_mat.quantity) != len(replacement_array): raise BadParameterError( _("ERR_CANNOT_PROPAGATE_MATERIAL_SIZES: %(material)s") % {'material': str(sch_mat.code)}) # update append_list for size_index, replace_mat in enumerate(replacement_array): quantities = [0] * len(replacement_array) quantities[size_index] = sch_mat.quantity[ size_index] if sch_mat.is_configurable else sch_mat.quantity[ 0] append_list.append( self.add_material(code=replace_mat, quantities=quantities, is_configurable=True, counter=sch_mat.counter, cost=sch_mat.cost, add=False)) # delete original one del self.materials[sch_mat_index] print '\toutput append_list', append_list print '\tbefore', self.materials self.materials.extend(append_list) print '\tafter\n', self.materials # If appended, this task must be converted to configurable if len(append_list) > 0 and not self.is_configurable: self.is_configurable = True self.duration = map(lambda _: self.duration[0], range(0, len(replacement_array))) self.staging_duration = map(lambda _: self.staging_duration[0], range(0, len(replacement_array)))
def room_dashboard(request, room_code, doc_no, action): try: room = Room.factory(room_code) except KeyError: raise BadParameterError(_("ERR_UNABLE_TO_IDENTIFY_ROOM")) if request.method == 'POST': if action is None: raise BadParameterError.required('action') if action == 'deliverable': """ POST Method, action="deliverable" Probe Job Tags for delivery candidates If condition met: all(previous_op.status == DELIVERED) and ClerkAuxTask.is_confirmed() then adjust operation status to READY. :returns [{}] deliverable """ if doc_no is None or len(doc_no) == 0: raise BadParameterError.required('production_order_doc_no') production_order = ProductionOrder.of('doc_no', doc_no) # User requested to update these task as "Ready" if production_order is None: raise BadParameterError( _("ERR_UNKNOWN_PRODUCTION_ORDER: %(doc_no)s") % {'doc_no': doc_no}) ready_operations = production_order.pending_tasks( lambda operation: operation.task in room.tasks) # Only previous operation of ready_operations where by ... # => prev_op.status == STATUS_CONFIRMED def traverse(): for ready_op in ready_operations: for prev_op in ready_op.previous_op(): if prev_op.status == ProductionOrderOperation.STATUS_CONFIRMED: yield prev_op return map( lambda a: { 'doc_no': a.doc_no, 'object_id': str(a.object_id), 'task': a.task.code }, traverse()) elif action == "delivered": """ POST Method, action="delivered" Assign Delivered status to provided doc_no, and set next_op to ready if possible... :raises BadParameter if doc_no is not ``TaskDocNo`` :returns Boolean """ if doc_no is None or len(doc_no) == 0: raise BadParameterError.required('task_doc_no') production_order_operation = ProductionOrderOperation.of( 'doc_no', doc_no) if production_order_operation is None: raise BadParameterError( _("ERR_UNKNOWN_PRODUCTION_ORDER_OPERATION_DOC_NO: %(doc_no)s" ) % {'doc_no': doc_no}) # Check if we can set this doc status to ready or not? if production_order_operation.status != ProductionOrderOperation.STATUS_CONFIRMED: raise ValidationError( _("ERR_UNABLE_TO_UPDATE_UNCONFIRMED_OPERATION_TO_DELIVERED: %(doc_no)s" ) % {'doc_no': doc_no}) # Update the status production_order_operation.status = ProductionOrderOperation.STATUS_DELIVERED production_order_operation.touched(request.user) production_order_operation.ready_next_operation_if_possible( author=request.user, context_room=room) return True raise BadParameterError( _("ERR_UNSUPPORTED_ACTION: %(action)s") % {'action': action}) if request.method == 'GET': # Query all parent task within the room results = ProductionOrderOperation.manager.find( cond={ 'planned_start': { '$lt': datetime.today().replace( hour=23, minute=59, second=59) }, '$and': [{ 'status': { '$gte': ProductionOrderOperation.STATUS_RELEASED } }, { 'status': { '$lt': ProductionOrderOperation.STATUS_CONFIRMED } }], 'task': { '$in': room.tasks } }) ref_docs = list(set(o.ref_doc[0] for o in results)) orders = ProductionOrder.manager.project({'_id': { '$in': ref_docs }}, project=['_id', 'doc_no']) # query clerk's auxiliary task status aux_tasks = ClerkAuxTask.manager.aggregate([{ "$group": { "_id": "$parent_task", "status": { "$min": "$status" } } }]) aux_tasks = dict((a['_id'], a['status']) for a in aux_tasks['result']) def process_task_output(a): r = a.serialized() # patch with previous_op statues previous_ops = [] for prev_op in a.previous_op(): previous_ops.append({ 'object_id': str(prev_op.object_id), 'task': prev_op.task.code, 'doc_no': prev_op.doc_no, 'status': prev_op.status }) r['previous_ops'] = previous_ops # patch with aux_task status key = a.object_id r['clerk_confirmed'] = aux_tasks[key] if key in aux_tasks else 0 return r return { 'orders': dict(map(lambda a: (str(a['_id']), a['doc_no']), orders)), 'tasks': map(process_task_output, results), 'activities': map(lambda a: a.as_json(), UserActiveTask.probe_all(results)) }
def return_materials(cls, user, doc_no, mode, return_list): """ Returning Materials - please see #return_candidates for more information. :param IntraUser user: :param basestring doc_no: :param basestring mode: :param list[dict[basestring, basestring]] return_list: :return: """ # Retrieve documents based on what it is given doc = Docs.of_doc_no(doc_no) # Validate input, # Extract source to compare current_list = [] ref_doc = None from_ref_doc = None if mode in [cls.TYPE_STAGED_IN_INVENTORY_CONTENT, cls.TYPE_STAGED_FROM_CLERK_AUX_TASK] and isinstance(doc, prod_doc.StoreAuxTask): o = cls.return_candidates(doc_no) current_list = list(o[0]['return_list']) doc.populate('parent_task') doc.parent_task.populate('ref_doc') ref_doc = doc.parent_task.ref_doc from_ref_doc = doc.parent_task elif mode == cls.TYPE_WIP_FROM_PRODUCTION_ORDER and isinstance(doc, prod_doc.ProductionOrderOperation): doc.populate('ref_doc') current_list = [{ 'material': 'WIP-CANDIDATE', 'quantity': doc.get_confirmable_quantity(), 'uom': doc.ref_doc.uom, 'revision': None, 'size': None, 'weight': 0, }] ref_doc = doc.ref_doc else: # Unsupported case raise BadParameterError(_("ERR_UNSUPPORTED_DOCUMENT_TYPE: %(document_type)s") % { 'document_type': str(type(doc)) }) # prepare movement based on return_list # Sanity check if len(current_list) != len(return_list): raise BadParameterError(_("ERR_UNEQUAL_SIZE_OF_RETURN_LIST: %(expected_size)s != $(actual_size)s") % { 'expected_size': len(current_list), 'actual_size': len(return_list) }) # index list for ease of comparison current_list = dict(map(lambda a: (str(a['material']), a), current_list)) return_list = dict(map(lambda a: (str(a['material']), a), return_list)) movements = [] entries = [] lost_entries = [] movement_type = None lost_movement_type = None # From STAGED => STORE # From STAGED => LOST & FOUND for key, value in current_list.iteritems(): lost = value['quantity'] - return_list[key]['quantity'] # NOTE: For WIP scenario if key == 'WIP-CANDIDATE': # init Goods receipt by-product for WIP to STORE # TODO: value for WIP to be fixed later with proper logic. now using static 543210. movement_type = stock_doc.InventoryMovement.GR_BP user.can(stock_doc.InventoryMovement.ACTION_WRITE(), movement_type, throw=True) # NOTE: Generate WIP Stock code wip_material = doc.ref_doc.material.create_wip(doc.task.code) # NOTE: Create material master from stock code stock_doc.MaterialMaster.factory(wip_material, uom=doc.ref_doc.uom, procurement_type=stock_doc.MaterialMaster.INTERNAL, author=user) current_list[key]['material'] = wip_material current_list[key]['uom'] = stock_doc.MaterialMaster.get(doc.ref_doc.material).uom.code entries.append(stock_doc.InventoryMovementEntry.factory(material=wip_material, quantity=return_list[key]['quantity'], location=Location.factory('STORE').code, weight=return_list[key]['weight'], value=543210)) if lost > 0: # if lost is found, init Goods receipt lost and found for WIP to STORE lost_movement_type = stock_doc.InventoryMovement.GR_LT lost_entries.append(stock_doc.InventoryMovementEntry.factory(material=wip_material, quantity=return_list[key]['quantity'], location=Location.factory('LOST').code, weight=return_list[key]['weight'], value=543210)) # NOTE: For STAGING scenario else: # init Stock transfer production to location for STAGING to STORE movement_type = stock_doc.InventoryMovement.ST_PL user.can(stock_doc.InventoryMovement.ACTION_WRITE(), movement_type, throw=True) entries.extend(stock_doc.InventoryMovementEntry.transfer_pair_factory(material=value['material'], quantity=return_list[key]['quantity'], from_location=Location.factory('STAGING').code, to_location=Location.factory('STORE').code, from_ref_doc=from_ref_doc)) if lost > 0: # if lost is found, init Stock transfer lost and found for STAGING to STORE retaining ref_doc lost_movement_type = stock_doc.InventoryMovement.ST_LT lost_entries.extend(stock_doc.InventoryMovementEntry.transfer_pair_factory(material=value['material'], quantity=return_list[key]['quantity'], from_location=Location.factory('STAGING').code, to_location=Location.factory('LOST').code, from_ref_doc=from_ref_doc, to_ref_doc=from_ref_doc)) if not entries: raise ValueError("Nothing to return") movement = stock_doc.InventoryMovement.factory(movement_type, list(entries), ref_doc=ref_doc) movements.append(movement) if lost > 0: user.can(stock_doc.InventoryMovement.ACTION_WRITE(), lost_movement_type, throw='challenge') movement = stock_doc.InventoryMovement.factory(lost_movement_type, list(lost_entries), ref_doc=ref_doc) movements.append(movement) if movements: # validate first then touched map(lambda x: x.validate(user=user), movements) # NOTE: create a new pair of store & clerk with different parent depending on mode if mode == cls.TYPE_STAGED_FROM_CLERK_AUX_TASK: task_signals.task_repeat.send(cls, parent=doc.parent_task, components=[v for i, v in current_list.iteritems()]) if mode == cls.TYPE_WIP_FROM_PRODUCTION_ORDER: task_signals.task_repeat.send(cls, parent=doc, components=[v for i, v in current_list.iteritems()]) doc.ready(user) map(lambda x: x.touched(user), movements) return len(movements)