def addFileObject(self, data): """ Upload the file object to OSS. For now we only accept images. - `data`: Content of the data to add as base64 encoded binary Returns the name of the uploaded object. """ data = b64decode(data) md5 = hashlib.md5(data).hexdigest() # first check duplication on the original file fname = self.getFileObjectByMd5(md5) if fname: return fname try: img = PIL.Image.open(io.BytesIO(data)) except OSError: raise RPCUserError('该文件不是图片格式。') if img.format not in ('JPEG', 'PNG', 'GIF'): raise RPCUserError('只支持jpg、png、gif格式的图片。') # images uploaded via `fileobject.add` shall already be optimized on # the client side and hence should not be optimized again return self.addImage(img, optimize=False, data=data)
def setBom(self, item, bomItems): """ Update the boms attribute of an item. The `bomItems` argument is a **list** of [item_id, quantity] which describes completely how the item is composed of. Set it to an empty list to remove bom if any were present. """ assert isinstance(bomItems, list), 'bomItems must be a list' if len(bomItems) == 1 and bomItems[0][1] == 1: raise RPCUserError('部件清单不能只有一个数量为1的项目。') assert item.itemId not in [i[0] for i in bomItems], \ 'Component item can not be the item itself' # check if item is already a component of another bi = self.sess.query(BomItem).filter( BomItem.componentItemId == item.itemId).first() if bi: raise RPCUserError('该商品已经是商品%d的部件。' % bi.itemId) weight = 0 for (iid, quantity) in bomItems: ci = self.sess.query(Item).get(iid) assert ci.isSku, 'Item %d is not a sku' % iid assert quantity, 'Quantity can not be 0' if not ci.weight: # ============================================================= # once set to None, weight remains None, since it # means some component has unknown weight # ============================================================= weight = None elif weight is not None: weight += ci.weight * quantity # remove all exisitng bom first if any is there if not item.isSku: item.boms.clear() item.isSku = True # now set the new bom for (idx, (iid, quantity)) in enumerate(bomItems): item.boms.append( BomItem(itemId=item.itemId, componentItemId=iid, quantity=quantity, componentOrder=idx)) item.isSku = False item.weight = weight
def payPurchaseOrder(self, orderId): sess = self.sess order = sess.query(PurchaseOrder).get(orderId) assert order, RPCUserError('订单号错误') assert order.orderStatus == ORDER_STATUS.COMPLETED, \ RPCUserError('订单未完成,不能付款') openId = order.supplier.wechatOpenId assert openId, RPCUserError('该供应商尚未关联微信') amount = order.payableAmount assert amount, RPCUserError('订单没有未付金额') params = { 'partner_trade_no': '%dT%d' % (order.orderId, time.time()), 'openid': openId, 'check_name': 'NO_CHECK', 'amount': int(amount * 100), 'desc': '大管家订单%d' % order.orderId, # this parameter is required but the value is not important 'spbill_create_ip': '192.168.0.1' } ret = wc.makeRequest('mmpaymkttransfers.promotion.transfers', params, cert=True) if ret.result_code == 'FAIL': raise RPCUserError(ret.err_code_des) payment = Payment(amount=amount, externalPaymentId=str(ret.payment_no), externalUserId=openId, payTime=datetime.strptime(str(ret.payment_time), '%Y-%m-%d %H:%M:%S'), partyId=order.supplierId, outTradeNo=str(ret.partner_trade_no), creatorId=self.request.user.partyId, paymentGateway=PAYMENT_GATEWAY.WEPAY, paymentMethod=27) sess.add(payment) sess.flush() sess.add( OrderPayment(orderId=order.orderId, paymentId=payment.paymentId, amount=amount)) order.paidAmount += amount
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 setImages(self, item, imageIds): """ Update the item image association. Note that for simplicity we use this function for all the add, insert, move, delete of item images. Just pass in a list of images that should be the correct one and we will set it. Note this function has nothing to do images used by the item's description modules""" # check that the main item image is square for iid in imageIds: image = self.sess.query(Image).get(iid) if abs(image.width - image.height) > 1: raise RPCUserError('商品主图必须是正方形。') if item.images: # this is necessary to load images first item.images.clear() if imageIds: for (idx, iid) in enumerate(imageIds): item.images.append( ItemImage(itemId=item.itemId, imageId=iid, imageOrder=idx)) mainImage = self.sess.query(Image).get(imageIds[0]) item.mainImageFile = '%s.%s' % \ (mainImage.imageId, mainImage.format) else: item.mainImageFile = None
def setItemStatus(self, item, status): # check existence of available image if status == ITEM_STATUS.ONLINE: if item.primaryCategoryId == 1810: raise RPCUserError('待审核商品%d不能上线!' % item.itemId) if not (item.sellingPrice and item.sellingPriceB): raise RPCUserError('不能上线无售价商品!') if not item.mainImageFile and ( not item.itemGroup or not item.itemGroup.shareImage or not self.sess.query(Item).get( item.itemGroup.items[0]).mainImageFile): raise RPCUserError('商品%d没有主图不能上线!' % item.itemId) item.itemStatus = status
def handleCompleteERPOrder(self, taskId, variables): # pylint: disable=W0613 orderId = variables['orderId'] so = self.sess.query(SalesOrder).get(orderId) if not so: raise RPCUserError(f'未找到ERP订单{orderId}') if so.orderStatus != ORDER_STATUS.COMPLETED: raise RPCUserError(f'销售订单{orderId}未完成') query = self.sess.query(PurchaseOrder).\ filter_by(relatedOrderId=orderId) orders = query.all() if not orders or \ [o for o in orders if o.orderStatus != ORDER_STATUS.COMPLETED]: raise RPCUserError(f'未找到该订单对应的采购订单或采购订单未完成')
def searchSku(self, text): """ Search sku with the given text. Used only by the `skuinput` widget If text is 8-digits number, it will be interpreted first as item id. Then we will search for items with text as the exact model_no. If there is an exact match, that match will be returned. If not found, we will search items with text as starting model_no. If still not found, search for items whose model_no or item_name contains the given text. Note that inactive items will not be returned. """ ret = set() query = self.sess.query(Item).filter( and_(Item.itemStatus != ITEM_STATUS.INACTIVE, Item.isSku == True)) if len(text) < 3: raise RPCUserError('商品型号最少长度为3个字符。') # find an exact match for model. Could be multiple items items = query.filter(func.upper(Item.model) == text.upper()).all() # if nothing found yet, match starting string of model if not items: items = query.filter( func.upper(func.substr(Item.model, 1, len(text))) == text.upper()).limit(11).all() if not items: items = query.filter( or_( Item.itemName.op('ilike')('%%%s%%' % text), Item.specification.op('ilike')('%%%s%%' % text), Item.model.op('ilike')('%%%s%%' % text))).limit(11).all() if len(items) > 10: return [] if items: for i in items: ret.add(i) if Item.IsItemNumber(text): item = self.sess.query(Item).get(text) if item: ret.add(item) return [{ 'itemId': i.itemId, 'itemName': i.itemName, 'model': i.model, 'specification': i.specification, 'unitId': i.unitId, 'weight': i.weight } for i in ret]
def save(self, kwargs): """ Creates a new or updates an existing article. TODO: The elasticsearch _id field is used to locate the article instead of the articleId field. It's confusing to use two different id for one object. A reindex should be able to migrate existing documents to using one articleId. Also the createDate field in the mapping is not used and shall be removed in a reindex. When creating a new article, fetch a sequence id from postgresql as its articleId. """ _id = kwargs.pop('_id') is_new = _id.startswith('Web.model') # is ths a new article? kwargs.pop('updateTime', None) # this shall be automatically set tags = kwargs.pop('tags', None) content = kwargs.get('content') # checks the content for external image urls if content: soup = BeautifulSoup(content, 'lxml') for img in soup.select('img'): host = urlparse(img['src']).netloc if not host.endswith('homemaster.cn'): raise RPCUserError('文章中不能包含外部图片链接!') if is_new: if kwargs['articleType'] == 'case': kwargs.pop('title') article = Article(**kwargs) article.updateTime = datetime.now() else: article = Article.get(_id) # update the article update time only when content is changed if article.content != kwargs.get('content'): article.updateTime = datetime.now() for (k, v) in kwargs.items(): setattr(article, k, v) if tags: article.tags = [tag.strip() for tag in tags.split(',')] # tags is explicitly cleared elif tags == '' and hasattr(article, 'tags'): del article.tags # generate an numberic article id for new articles if is_new: article.articleId = self.sess.execute(Sequence('resource_id_seq')) article.save() return self.getData(article)
def updateContent(self, ig, content): for f in ('groupBy', 'shareDescription', 'shareImage', 'groupItemName', 'groupImageId'): setattr(ig, f, content.get(f)) # check if any item is already a member of other group iids = [i['itemId'] for i in content['items']] items = self.sess.query(Item).filter( and_(Item.itemId.in_(iids), Item.itemGroupId != ig.itemGroupId)).all() if items: raise RPCUserError('商品%d已定义商品组合' % items[0].itemId) firstItem = self.sess.query(Item).get(iids[0]) if ig.shareImage and not firstItem.mainImageUrl: raise RPCUserError('商品组合第一个商品没有主图') if ig.shareDescription and not firstItem.descriptionModules: raise RPCUserError('商品组合第一个商品没有描述') if ig.groupImageId and not \ self.sess.query(ItemImage).filter( ItemImage.imageId == ig.groupImageId).all(): raise RPCUserError('图片id不存在或不是商品的主图') if ig.groupBy == 'L': if not content['labelName'].strip(): raise RPCUserError('标签名称不能为空') ig.groupCriteria = { 'labelName': content['labelName'], 'labels': [i['label'] for i in content['items']], 'thumbs': [i['thumb'] for i in content['items']] } ig.items = [i['itemId'] for i in content['items']]
def userLogin(request, login, password, appName): """ This and 'auth.logout' are the only rpc methods that does not subclass RpcBase for they must be invoked without valid user session. """ sess = DBSession() user = sess.query(Party).filter_by(login=login).first() if not user or not user.verifyPassword(password): raise RPCUserError('登录失败,请检查用户名和密码!') # Only those with defined permission are allowed if not user.extraData or appName not in user.extraData: raise RPCNotAllowedError('您无权登录当前应用。') # after user is authenticated, we cache the user object in redis sess.expunge_all() # detach from session request.session['user'] = user # copy so that we do not persist the token in the session user object ret = user.extraData[appName].copy() ret['csrfToken'] = get_csrf_token(request) return ret
def checkAndConvertImage(self, data, fname, imgType): """ Helper function that returns a **PIL.Image** object and the binary image data of subject to the following transformations: * if the images is in PSD format, convert it to PNG and extract text content from the file * for item image, image are sized down to 800x800 or 1200x1200 depending on the original size. If the input image is not square, it will be centered appropriately vertically or horizontally. * for description image, if the image width exceeds 790, it is resized to 790 while keeping the image aspect ratio """ data = b64decode(data) if fname.endswith('.psd'): psd = PSDImage.from_stream(io.BytesIO(data)) content = extractTextFromPSD(psd) img = psd.as_PIL() else: try: img = PIL.Image.open(io.BytesIO(data)) except OSError: raise RPCUserError('该文件不是图片格式。') if img.format not in ('JPEG', 'PNG', 'GIF'): raise RPCUserError('只支持jpg、png、gif格式的图片。') content = None modified = False if imgType == 'item': if img.width < 800 and img.height < 800: raise RPCUserError('商品主图宽度和高度必须至少有一个超过800。') if len(data) > 500 * 1024: raise RPCUserError('商品主图大小不能超过500KB。') out_size = 1200 if img.width >= 1200 or img.height >= 1200 else 800 ratio = min(out_size / img.width, out_size / img.height) w, h = int(img.width * ratio), int(img.height * ratio) if w != out_size or h != out_size: img = img.resize((w, h), PIL.Image.LANCZOS) out_img = PIL.Image.new('RGB', (out_size, out_size), 'white') out_img.paste(img, ((out_size - w) // 2, (out_size - h) // 2)) modified = True img = out_img else: # this is image for description if img.width < 790: raise RPCUserError('商品描述图片宽度最小为790。') if not 0.3 <= img.height / img.width <= 2: raise RPCUserError('商品描述图片高宽比(高度/宽度)必须在0.3-2之间。') if img.width > 790: img = img.resize((790, int(790 / img.width * img.height)), PIL.Image.LANCZOS) modified = True if modified: img.format = 'JPEG' stream = io.BytesIO() img.save(stream, img.format, optimize=True, quality=95) data = stream.getvalue() if len(data) > 2 * 1024 * 1024: raise RPCUserError('图片大小不能超过2MB。') return img, data, content
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 searchItem(self, text=None, brandId=None, catId=None, status=None, isSku=False, assortmentOnly=False): """ Search items based on given conditions. When status is not given, no inactive items will be returned. To search for items of all status, set status to 'all'. Returns a list or found Items. If not found, an empty list is returned. """ query = self.sess.query(Item) if not (catId or text or brandId): raise RPCUserError('请指定搜索条件!') if catId: cs = categoryFactory() node = cs.get(int(catId)) query = query.filter( Item.primaryCategoryId.in_([n.key for n in node.leafNodes])) if text: or_clause = [ Item.itemName.op('ilike')('%%%s%%' % text), Item.specification.op('ilike')('%%%s%%' % text), Item.model.op('ilike')('%%%s%%' % text) ] query = query.filter(or_(*or_clause)) if brandId: query = query.filter_by(brandId=brandId) if isSku: query = query.filter_by(isSku=isSku) if assortmentOnly: cs = categoryFactory() query = query.filter( not_( Item.primaryCategoryId.in_( [n.key for n in cs.get(1800).leafNodes]))) if status is None: query = query.filter(Item.itemStatus != ITEM_STATUS.INACTIVE) elif isinstance(status, int): query = query.filter_by(itemStatus=status) elif status == 'all': pass else: raise ValueError('Invalid status parameter') items = query.all() # if the search text could be an item number, we append it to the # search result. if text and Item.IsItemNumber(text): item = self.sess.query(Item).get(int(text)) if item: items.append(item) return self.marshall(items)
def download(self, itemId, itemUrl): host = urlparse(itemUrl).netloc if host == 'item.taobao.com': raise RPCUserError('不支持从集市店铺导入商品描述。') elif host != 'detail.tmall.com': raise RPCUserError('商品链接不正确。') item = self.sess.query(Item).get(itemId) assert item, 'Invalid itemId' ret = requests.get(itemUrl) if not ret.ok: raise RPCUserError('链接访问失败,请确定商品链接是否正确。') itemInfo = parseTmall(ret.text) # # Processing item image # imageIds = [] for imgurl in itemInfo['images']: image = None ret = requests.get(imgurl) data = ret.content if not ret.ok or ret.headers['Content-Type'] not in \ ('image/jpeg', 'image/png', 'image/gif'): continue image = self.findImage(data) if not image: img = PIL.Image.open(io.BytesIO(data)) if img.width < 600 and img.height < 600: continue image = self._uploadImage(img, data) imageIds.append(image.imageId) if imageIds: self.setImages(item, imageIds) modules = {} for (mid, mimages) in itemInfo['modules'].items(): if mid not in __desc_module_order__: continue imageIds = [] for imgUrl in mimages: image = None ret = requests.get(imgUrl) data = ret.content if not ret.ok or ret.headers['Content-Type'] not in \ ('image/jpeg', 'image/png', 'image/gif'): continue image = self.findImage(data) if not image: img = PIL.Image.open(io.BytesIO(data)) if img.width < 750 or img.height / img.width < 0.3: continue if img.width != 790: fmt = img.format # after resize, format would be lost img = img.resize( (790, int(790 / img.width * img.height)), PIL.Image.LANCZOS) img.format = fmt stream = io.BytesIO() img.save(stream, img.format) data = stream.getvalue() image = self.findImage(data) if not image: image = self._uploadImage(img, data) else: image = self._uploadImage(img, data) imageIds.append(image.imageId) if imageIds: modules[mid] = imageIds if modules: item.descriptionModules = modules
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 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))