def add_variables(): """ For **active** process instances of the last version, add the variables: actualMeasurementDate shippingDate We do not add actualInstallationDate since those process instances are already completed. """ # get a list of all active processIds, since we can not put variable on # already completed process instances processes = [p['id'] for p in cc.makeRequest('/process-instance', 'post', { 'processDefinitionKey': 'worktop' })] for (activity, var) in ( ('TakeMeasurement', 'actualMeasurementDate'), ('WorktopShipped', 'shippingDate'), ): for act in [a for a in cc.makeRequest( '/history/activity-instance', 'post', { 'processDefinitionKey': 'worktop', 'activityId': activity, 'finished': True } ) if not a['canceled']]: if act['processInstanceId'] in processes: cc.makeRequest( f'/process-instance/{act["processInstanceId"]}' f'/variables/{var}', 'put', params={ 'value': act['endTime'], 'type': 'Date' })
def completeTask(self, taskId, taskKey, variables=None): """ Complete the given task and change the process variables. :return: True if task completes successfully. False if status 500 is returned by camunda, usually because the task is already completed by some other user :raises: RPCUserError when anything else happens """ # parse all variable fields whose name ends with Date as date # we'd better check against a process variable repository to find # the type in stead of relying on variable naming if variables: variables = parseDate(variables, fields=[ fname for fname in variables if fname.endswith('Date') ]) handler = getattr(self, 'handle' + taskKey, None) if handler: handler(taskId, variables) try: cc.makeRequest(f'/task/{taskId}/complete', 'post', variables=variables) except CamundaRESTError as e: if e.status == 500: return False else: raise RPCUserError('无法完成该任务,请联系技术支持') return True
def getTask(self, taskId): """ Return a single task by task id together with all process variables of the process to which the task belongs. If the task can no longer be found, maybe completed by someone else, return None. """ try: task = cc.makeRequest(f'/task/{taskId}', 'get', withProcessVariables='*') except CamundaRESTError as e: if e.status == 404: task = None else: raise if task: task['comments'] = cc.makeRequest(f'/task/{taskId}/comment', 'get') ois = task['processVariables'].get('orderItems') if ois: addItemInfo(ois) return task
def do_main(): ret = cc.makeRequest('/process-instance', 'post', {'processDefinitionKey': 'worktop'}) for p in ret: cc.makeRequest( f'/process-instance/{p["id"]}/variables/orderId', 'put', params={ 'value': int(p['businessKey']), 'type': 'Integer' } )
def migrate_process(): plan = cc.makeRequest('/migration/generate', 'post', params={ 'sourceProcessDefinitionId': SOURCE_ID, 'targetProcessDefinitionId': TARGET_ID }) cc.makeRequest('/migration/execute', 'post', params={ "skipCustomListeners": True, "processInstanceQuery": { "processDefinitionId": SOURCE_ID }, "migrationPlan": plan })
def __call__(self): request = self.request ret = cc.makeRequest( '/variable-instance', 'post', {'processInstanceIdIn': [request.matchdict['processId']]}, urlParams={'deserializeValues': 'false'}) if not ret: raise HTTPNotFound() self.variables = cc.parseVariables(ret) ois = self.variables.get('orderItems') if ois: addItemInfo(ois) stream = self.template.generate(variables=self.variables, view=self) body = rml2pdf.parseString(stream.render()).read() response = Response( content_type='application/pdf', content_disposition='filename="%s_%s.pdf"' % (self.variables['externalOrderId'], request.matchdict['form']), content_length=len(body), body=body) return response
def getTaskList(self): """ Return all active tasks of a process key. In additional to the properties of the Camunda task object, we also return serveral additional process variables related to the task, like externalOrderId and customerName in a `processVariables` attribute. """ params = { 'processDefinitionKey': 'worktop', 'sorting': [{ 'sortBy': 'name', 'sortOrder': 'asc' }, { 'sortBy': 'dueDate', 'sortOrder': 'asc' }] } if not hasRole(self.request.user, 'admin'): params['taskDefinitionKeyIn'] = getUserTasks(self.request.user) tasks = cc.makeRequest('/task', 'post', params, withProcessVariables=('externalOrderId', 'customerName', 'customerRegionCode')) for t in tasks: var = t['processVariables'] var['customerRegionName'] = getRegionName( var['customerRegionCode']) return tasks
def getOutstandingOrders(self): params = { 'processDefinitionKey': 'worktop', 'activityId': 'WorktopShipped', 'unfinished': True } ret = cc.makeRequest( '/history/activity-instance', 'post', params, urlParams={'maxResults': 100}, withProcessVariables=('orderId', 'externalOrderId', 'factoryNumber', 'customerName', 'customerRegionCode', 'scheduledInstallationDate', 'productionDrawing', 'orderItems'), hoistProcessVariables=True) for p in ret: ois = p.get('orderItems') if ois: addItemInfo(ois) if 'customerRegionCode' in p: p['customerRegionName'] = getRegionName( p['customerRegionCode']) del p['customerRegionCode'] return ret
def __call__(self): assert 'orders' in self.request.params oids = self.request.params['orders'].split(',') assert oids labels = [] for oid in oids: ret = cc.makeRequest('/process-instance', 'post', {'businessKey': oid}, withProcessVariables=( 'orderId', 'externalOrderId', 'factoryNumber', 'customerName', 'customerRegionCode', ), processInstanceIdField='id', hoistProcessVariables=True) pkgId = ret[0]['pkgId'] = shortuuid.uuid() ret[0]['labelUrl'] = f'{self.request.host_url}/ikea/shipOrder?' \ f'orderId={oid}&pkgId={pkgId}' labels.append(ret[0]) loader = TemplateLoader([os.path.dirname(__file__)]) template = loader.load('shippingLabel.rml') stream = template.generate(labels=labels, view=self) body = rml2pdf.parseString(stream.render()).read() response = Response(content_type='application/pdf', content_disposition='filename="labels.pdf"', content_length=len(body), body=body) return response
def add_variables(): """ For **active** process instances of the last version set the variable isInstallationRequested to 'true'. """ # get a list of all active processIds, since we can not put variable on # already completed process instances processes = [p['id'] for p in cc.makeRequest('/process-instance', 'post', { 'processDefinitionKey': 'worktop' })] for pid in processes: cc.makeRequest( f'/process-instance/{pid}/variables/isInstallationRequested', 'put', params={ 'value': 'true', 'type': 'Boolean' } )
def changeTaskDueDate(self, taskId, dueDate, justification): """ :param dueDate: a javascript object string, could be in UTC so careful """ # to change the task due, we have to get the full task object and # modify the due, since the PUT method replace the task object as a # whole, not just one field task = cc.makeRequest(f'/task/{taskId}', 'get') newDue = parser.parse(dueDate).astimezone(tz.tzlocal()).replace( hour=23, minute=59, second=59) task['due'] = newDue.strftime('%Y-%m-%dT%H:%M:%S.0+0800') cc.makeRequest(f'/task/{taskId}', 'put', task) comment = '\n'.join(('任务期限从{old}修改为{new}'.format( old=parser.parse(task['due']).astimezone( tz.tzlocal()).strftime('%Y-%m-%d %H:%M:%S'), new=newDue.strftime('%Y-%m-%d %H:%M:%S')), '修改原因: ' + justification)) self.addTaskComment(taskId, comment)
def getProcessVariables(self, processInstanceId): variables = cc.parseVariables( cc.makeRequest(f'/history/variable-instance', 'post', {'processInstanceIdIn': [processInstanceId]}, urlParams={'deserializeValues': 'false'})) ois = variables.get('orderItems') if ois: addItemInfo(ois) return variables
def __call__(self): key = self.request.matchdict['key'] if key == 'current': today = date.today() self.startDate = today - timedelta(today.isocalendar()[2] + 6) else: assert len(key) == 6 and key.isnumeric() year, week = int(key[:4]), int(key[4:]) assert week <= 52 and year >= 2018 and year <= 2050 self.startDate = Week(year, week).monday() self.endDate = self.startDate + timedelta(6) cal = self.startDate.isocalendar() self.docKey = f'{cal[0]}/{cal[1]}' self.orders = self.sess.query(SalesOrder).\ filter_by(orderSource=ORDER_SOURCE.IKEA).\ filter_by(orderStatus=ORDER_STATUS.COMPLETED).\ filter(SalesOrder.completionDate >= self.startDate).\ filter(SalesOrder.completionDate < self.endDate + timedelta(1)).\ all() self.total = sum([o.amount for o in self.orders]) for o in self.orders: ret = cc.makeRequest( '/history/process-instance', 'post', {'processInstanceBusinessKey': o.orderId}, withProcessVariables=('storeId', ), processInstanceIdField='id', hoistProcessVariables=True )[0] o.storeId = ret['storeId'] self.orders.sort(key=lambda o: (o.storeId, o.orderId)) loader = TemplateLoader([os.path.dirname(__file__)]) template = loader.load('receivable.pt') stream = template.generate(view=self) body = rml2pdf.parseString(stream.render()).read() response = Response( content_type='application/pdf', content_disposition=f'filename="AR{self.docKey}.pdf"', content_length=len(body), body=body) return response
def receiveWorktop(self, orderId, pkgId): """ For orders with multpile packages, the method will record all received packages in the process variable `receivedPackages` :param orderId: The ERP order id :param pkgId: Package id as generated by shortuuid """ # first check the order is still active process = cc.makeRequest( '/process-instance', 'post', params={'businessKey': orderId}, withProcessVariables=('receivedPackages', 'externalOrderId', 'customerName', 'customerMobile', 'customerRegionCode'), processInstanceIdField='id') # this could be the scanning of an old label for a process already # completed if len(process) != 1: raise RPCUserError(f'订单{orderId}不在待收货状态') process = process[0] # the already received packages pkgs = process['processVariables'].get('receivedPackages', []) if pkgId in pkgs: raise RPCUserError('该件已收货,请勿重复收货') # now check if the process is waiting for the receive signal. If # there is any packages already received for the order, it will be no # longer waiting for the receive signal exe = cc.makeRequest('/execution', 'post', params={ 'signalEventSubscriptionName': 'WorktopReceived', 'processDefinitionKey': 'worktop', 'businessKey': orderId }) if not exe and not pkgs: raise RPCUserError(f'订单{orderId}不在待收货状态') # this should not be valid combination if exe and pkgs: raise RPCUserError(f'系统错误') if len(exe) == 1: cc.makeRequest('/signal', 'post', params={ 'name': 'WorktopReceived', 'executionId': exe[0]['id'] }) # update the process's receivedPackages variable pkgs.append(pkgId) cc.makeRequest( f'/process-instance/{process["id"]}' '/variables/receivedPackages', 'put', params=cc.convertVariables({'var': pkgs})['var']) # return the order header for confirmation return process['processVariables']
def searchProcess(cond, request, countOnly=False, maxRows=50): """ Note the startDate and endDate will be passed in UTC """ cond['storeId'] = request.user.extraData['worktop'].get('storeId') params = { 'processDefinitionKey': 'worktop', 'sorting': [{ 'sortBy': 'startTime', 'sortOrder': 'desc' }] } searchText = cond.get('searchText') showCompleted = cond.get('completed') if searchText: if isOrderId(searchText): params['processInstanceBusinessKey'] = searchText elif isIkeaOrderId(searchText): params['variables'] = [{ 'name': 'externalOrderId', 'operator': 'eq', 'value': searchText.upper() }] elif isMobile(searchText): params['variables'] = [{ 'name': 'customerMobile', 'operator': 'eq', 'value': searchText }] else: params['variables'] = [{ 'name': 'customerName', 'operator': 'eq', 'value': searchText }] else: parseDate(cond, fields=['startDate', 'endDate']) startDate, endDate = cond['startDate'], cond['endDate'] endDate = endDate + timedelta(1) if (endDate - startDate) > timedelta(365): raise RPCUserError('订单查询时间跨度不能大于1年。') if showCompleted: params['variables'] = [{ 'name': 'actualInstallationDate', 'operator': 'gteq', 'value': startDate }, { 'name': 'actualInstallationDate', 'operator': 'lt', 'value': endDate }] else: params['startedBefore'] = endDate params['startedAfter'] = startDate storeId = cond['storeId'] if storeId: variables = params.setdefault('variables', []) variables.append({ 'name': 'storeId', 'operator': 'eq', 'value': storeId }) if countOnly: ret = cc.makeRequest( '/history/process-instance/count', 'post', params, ) if ret['count'] > 500: raise RPCUserError('单次导出结果大于500条,请搜索条件再') return ret['count'] else: ret = cc.makeRequest( '/history/process-instance', 'post', params, urlParams={'maxResults': maxRows}, withProcessVariables=('externalOrderId', 'customerName', 'storeId', 'orderItems', 'receivingDate', 'isInstallationRequested', 'actualMeasurementDate', 'confirmedMeasurementDate', 'scheduledMeasurementDate', 'actualInstallationDate', 'confirmedInstallationDate', 'scheduledInstallationDate'), processInstanceIdField='id', hoistProcessVariables=True) # # Prepare for display by adding additional infos: # * add model to orderItems as only itemId is stored # * add human readable status text # currentTime = datetime.now() for p in ret: # The instance variables of Date type are parsed correctly, but the # process property is not. We will do the parse here. parseDate(p, fields=['startTime']) p['startTime'] = p['startTime'].astimezone(tzLocal).replace( tzinfo=None) # calculate the duration of the process. if 'TERMINATED' not in p['state']: start = p.get('actualMeasurementDate') or \ p.get('scheduledMeasurementDate') or p['startTime'] end = p.get('actualInstallationDate') or currentTime delta = end - start if delta.total_seconds() > 0: p['duration'] = delta.days + decimal_round( Decimal(delta.seconds / 86400), Decimal('0.1')) state = p.pop('state') if state == 'ACTIVE': if p.get('actualInstallationDate'): text = '已安装' \ if p.get('isInstallationRequested', True) else '已送货' elif p.get('confirmedInstallationDate'): text = '待安装' elif p.get('receivingDate'): text = '已收货' elif p.get('actualMeasurementDate') or \ not p.get('scheduledMeasurementDate'): text = '生产中' elif p.get('confirmedMeasurementDate') or \ p.get('scheduledMeasurementDate'): text = '待测量' else: text = '进行中' p['statusText'] = text else: p['statusText'] = __StatusNames__[state] ois = p.get('orderItems') if ois: addItemInfo(ois) return ret
def startProcess(self, params): params = parseDate( params, fields=['scheduledMeasurementDate', 'scheduledInstallationDate']) if not params['scheduledInstallationDate']: params.pop('scheduledInstallationDate') if not params['isMeasurementRequested']: params.pop('scheduledMeasurementDate', None) params['productionDrawing'] = params['orderFile'] externalOrderId = params.get('externalOrderId') if externalOrderId and \ self.sess.query(SalesOrder).filter_by( orderSource=ORDER_SOURCE.IKEA, externalOrderId=externalOrderId ).all(): raise RPCUserError('该订单号已存在,不能重复提交') order = SalesOrder(customerId=int(SPECIAL_PARTY.BE), creatorId=self.request.user.partyId, regionCode=params['customerRegionCode'], streetAddress=params['customerStreet'], recipientName=params['customerName'], recipientMobile=params['customerMobile'], orderSource=ORDER_SOURCE.IKEA) if params['isMeasurementRequested']: itemMeasure = self.sess.query(Item).get(10023928) order.addItem(item=itemMeasure, quantity=1) serviceItem = self.sess.query(Item).get( 10023929 if params['isInstallationRequested'] else 10024028) order.addItem(item=serviceItem, quantity=1) self.sess.add(order) self.sess.flush() po = PurchaseOrder(supplierId=10025188, customerId=int(SPECIAL_PARTY.BE), creatorId=self.request.user.partyId, regionCode=params['customerRegionCode'], streetAddress=params['customerStreet'], recipientName=params['customerName'], recipientMobile=params['customerMobile'], relatedOrderId=order.orderId) if params['isMeasurementRequested']: po.addItem(item=itemMeasure, quantity=1) po.addItem(item=serviceItem, quantity=1) self.sess.add(po) if externalOrderId: order.externalOrderId = externalOrderId else: params['externalOrderId'] = str(order.orderId) # add orderId also as a process instance variable params['orderId'] = order.orderId cc.makeRequest(f'/process-definition/key/worktop/start', 'post', {'businessKey': order.orderId}, variables=params)
def addTaskComment(self, taskId, comment): cc.makeRequest(f'/task/{taskId}/comment/create', 'post', {'message': comment})
def shipWorktop(self, extOrderIds): """ Send the WorktopShipped signal to all waiting processes given by the orderId list. :param orderIds: A list of externalOrderId :raises RPCUserError: Raises RPCUserError if any order can not be shipped or if any error occurs during the signal sending to any order. """ errors = [] executions = [] extOrderIds = set(extOrderIds) # deduplicate for externalOrderId in extOrderIds: ret = cc.makeRequest( '/execution', 'post', params={ 'signalEventSubscriptionName': 'WorktopShipped', 'processDefinitionKey': 'worktop', 'processVariables': [{ 'name': 'externalOrderId', 'operator': 'eq', 'value': externalOrderId }] }, withProcessVariables=('externalOrderId', 'scheduledInstallationDate'), hoistProcessVariables=True) if not ret: errors.append(f'未找到待发货的订单{externalOrderId}') elif len(ret) != 1: errors.append(f'订单{externalOrderId}无法发货发货') else: executions.append(ret[0]) if errors: raise RPCUserError('\n'.join(errors)) for exe in executions: try: # TODO: this is a temporary fix so that process without # scheduledInstallationDate can continue past into next step by # defaulting the date to 3 days from shipment date variables = cc.convertVariables( {'scheduledInstallationDate': date.today() + timedelta(3)}) cc.makeRequest( f'/process-instance/{exe["processInstanceId"]}' '/variables/scheduledInstallationDate', 'put', params=variables['scheduledInstallationDate']) cc.makeRequest('/signal', 'post', params={ 'name': 'WorktopShipped', 'executionId': exe['id'] }) except CamundaRESTError: errors.append(f"订单{exe['externalOrderId']}发货错误") if errors: raise RPCUserError('\n'.join(errors))