ticket_base_service.py 132 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571
  1. import copy
  2. import json
  3. import datetime
  4. import logging
  5. import random
  6. import redis
  7. from django.db.models import Q
  8. from django.conf import settings
  9. from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
  10. from apps.workflow.models import CustomField
  11. from apps.ticket.models import TicketRecord, TicketCustomField, TicketFlowLog, TicketUser
  12. from service.redis_pool import POOL
  13. from service.base_service import BaseService
  14. from service.common.log_service import auto_log
  15. from service.common.common_service import common_service_ins
  16. from service.common.constant_service import constant_service_ins
  17. from service.account.account_base_service import account_base_service_ins
  18. from service.workflow.workflow_base_service import workflow_base_service_ins
  19. from service.workflow.workflow_state_service import workflow_state_service_ins
  20. from service.workflow.workflow_transition_service import workflow_transition_service_ins
  21. from service.workflow.workflow_custom_field_service import workflow_custom_field_service_ins
  22. class TicketBaseService(BaseService):
  23. """
  24. 工单基础服务
  25. """
  26. def __init__(self):
  27. pass
  28. @classmethod
  29. @auto_log
  30. def get_ticket_by_id(cls, ticket_id: int)->tuple:
  31. """
  32. 获取工单对象
  33. :param ticket_id:
  34. :return:
  35. """
  36. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  37. if ticket_obj:
  38. return True, ticket_obj
  39. else:
  40. return False, 'ticket is not existed or has been deleted'
  41. @classmethod
  42. @auto_log
  43. def get_ticket_list(cls, sn: str='', title: str='', username: str='', create_start: str='', create_end: str='',
  44. workflow_ids: str='', state_ids: str='', ticket_ids: str='', category: str='', reverse: int=1,
  45. per_page: int=10, page: int=1, app_name: str='', **kwargs):
  46. """
  47. 工单列表
  48. :param sn:
  49. :param title:
  50. :param username:
  51. :param create_start: 创建时间起
  52. :param create_end: 创建时间止
  53. :param workflow_ids: 工作流id,str,逗号隔开
  54. :param state_ids: 状态id,str,逗号隔开
  55. :param ticket_ids: 工单id,str,逗号隔开
  56. :param category: 查询类别(创建的,待办的,关联的:包括创建的、处理过的、曾经需要处理但是没有处理的, 我处理过的)
  57. :param reverse: 按照创建时间倒序
  58. :param per_page:
  59. :param page:
  60. :param app_name:
  61. act_state_id: int=0 进行状态, 0 草稿中、1.进行中 2.被退回 3.被撤回 4.已完成 5.已关闭
  62. :return:
  63. """
  64. category_list = ['all', 'owner', 'duty', 'relation', 'worked', 'view', 'intervene']
  65. if category not in category_list:
  66. return False, 'category value is invalid, it should be in all, owner, duty, relation'
  67. query_params = Q(is_deleted=False)
  68. # 获取调用方app_name 有权限的workflow_id_list
  69. from service.workflow.workflow_permission_service import workflow_permission_service_ins
  70. flag, result = workflow_permission_service_ins.get_workflow_id_list_by_permission('api', 'app', app_name)
  71. if not flag or not result.get('workflow_id_list'):
  72. return True, dict(ticket_result_restful_list=[], paginator_info=dict(per_page=per_page, page=page, total=0))
  73. else:
  74. app_workflow_id_list = result.get('workflow_id_list')
  75. # query_params &= Q(workflow_id__in=result.get('workflow_id_list'))
  76. if kwargs.get('act_state_id') != '':
  77. query_params &= Q(act_state_id=int(kwargs.get('act_state_id')))
  78. if kwargs.get('from_admin') != '':
  79. # 管理员查看, 获取其有权限的工作流列表
  80. flag, result = workflow_base_service_ins.get_workflow_manage_list(username
  81. )
  82. if flag is False:
  83. return False, result
  84. workflow_list = result.get('workflow_list')
  85. workflow_admin_id_list = [workflow['id'] for workflow in workflow_list]
  86. # query_params &= Q(workflow_id__in=workflow_admin_id_list)
  87. if kwargs.get('creator') != '':
  88. query_params &= Q(creator=kwargs.get('creator'))
  89. if kwargs.get('parent_ticket_id'):
  90. query_params &= Q(parent_ticket_id=kwargs.get('parent_ticket_id'))
  91. if kwargs.get('parent_ticket_state_id'):
  92. query_params &= Q(parent_ticket_state_id=kwargs.get('parent_ticket_state_id'))
  93. if sn:
  94. query_params &= Q(sn__startswith=sn)
  95. if title:
  96. query_params &= Q(title__contains=title)
  97. if create_start:
  98. query_params &= Q(gmt_created__gte=create_start)
  99. if create_end:
  100. query_params &= Q(gmt_created__lte=create_end)
  101. if workflow_ids:
  102. workflow_id_str_list = workflow_ids.split(',')
  103. query_workflow_id_list = [int(workflow_id_str) for workflow_id_str in workflow_id_str_list]
  104. else:
  105. query_workflow_id_list = []
  106. # query_params &= Q(workflow_id__in=workflow_id_list)
  107. if state_ids:
  108. state_id_str_list = state_ids.split(',')
  109. state_id_list = [int(state_id_str) for state_id_str in state_id_str_list]
  110. query_params &= Q(state_id__in=state_id_list)
  111. if ticket_ids:
  112. ticket_id_str_list = ticket_ids.split(',')
  113. ticket_id_list = [int(ticket_id_str) for ticket_id_str in ticket_id_str_list]
  114. query_params &= Q(id__in=ticket_id_list)
  115. if kwargs.get('from_admin'):
  116. permission_workflow_id_set = set(workflow_admin_id_list) - (set(workflow_admin_id_list) - set(app_workflow_id_list))
  117. if query_workflow_id_list:
  118. ending_workflow_id_list = list(permission_workflow_id_set - (permission_workflow_id_set - set(query_workflow_id_list)))
  119. else:
  120. ending_workflow_id_list = list(permission_workflow_id_set)
  121. else:
  122. if query_workflow_id_list:
  123. ending_workflow_id_list = list(set(app_workflow_id_list) - (set(app_workflow_id_list) - set(query_workflow_id_list)))
  124. else:
  125. ending_workflow_id_list = app_workflow_id_list
  126. query_params &= Q(workflow_id__in=ending_workflow_id_list)
  127. if reverse:
  128. order_by_str = '-gmt_created'
  129. else:
  130. order_by_str = 'gmt_created'
  131. if category == 'owner':
  132. query_params &= Q(creator=username)
  133. ticket_objects = TicketRecord.objects.filter(query_params).order_by(order_by_str).distinct()
  134. elif category == 'duty':
  135. # 为了加快查询速度,该结果从ticket_usr表中获取。 对于部门、角色、这种处理人类型的,工单流转后 修改了部门或角色对应的人员会存在这些人无法在待办列表中查询到工单
  136. duty_query_expression = Q(ticketuser__in_process=True, ticketuser__username=username)
  137. query_params &= duty_query_expression
  138. act_state_expression = ~Q(act_state_id__in=[
  139. constant_service_ins.TICKET_ACT_STATE_FINISH,
  140. constant_service_ins.TICKET_ACT_STATE_CLOSED
  141. ])
  142. query_params &= act_state_expression
  143. ticket_objects = TicketRecord.objects.filter(query_params).order_by(order_by_str).distinct()
  144. elif category == 'relation':
  145. relation_query_expression = Q(ticketuser__username=username)
  146. query_params &= relation_query_expression
  147. ticket_objects = TicketRecord.objects.filter(query_params).order_by(order_by_str).distinct()
  148. elif category == 'worked':
  149. worked_query_expression = Q(ticketuser__username=username, ticketuser__worked=True)
  150. query_params &= worked_query_expression
  151. ticket_objects = TicketRecord.objects.filter(query_params).order_by(order_by_str).distinct()
  152. elif category in ('view', 'intervene'):
  153. flag, result = workflow_permission_service_ins.get_workflow_id_list_by_permission(category, 'user', username)
  154. if not flag:
  155. view_workflow_ids = []
  156. else:
  157. view_workflow_ids = result.get('workflow_id_list', [])
  158. view_department_workflow_id_list = []
  159. if category == 'view':
  160. # view 还需要考虑查看权限部门,先查询用户所在部门,然后查询
  161. flag, result = account_base_service_ins.get_user_up_dept_id_list(username)
  162. if flag:
  163. department_id_str_list = [str(result0) for result0 in result]
  164. flag, result = workflow_permission_service_ins.get_workflow_id_list_by_permission(
  165. category, 'department', ','.join(department_id_str_list))
  166. view_department_workflow_id_list = result.get('workflow_id_list', []) if flag else []
  167. category_workflow_ids = list(set(view_workflow_ids).union(set(view_department_workflow_id_list)))
  168. view_query_expression = Q(workflow_id__in=category_workflow_ids)
  169. query_params &= view_query_expression
  170. ticket_objects = TicketRecord.objects.filter(query_params).order_by(order_by_str).distinct()
  171. else:
  172. ticket_objects = TicketRecord.objects.filter(query_params).order_by(order_by_str).distinct()
  173. paginator = Paginator(ticket_objects, per_page)
  174. try:
  175. ticket_result_paginator = paginator.page(page)
  176. except PageNotAnInteger:
  177. ticket_result_paginator = paginator.page(1)
  178. except EmptyPage:
  179. # If page is out of range (e.g. 9999), deliver last page of results
  180. ticket_result_paginator = paginator.page(paginator.num_pages)
  181. ticket_result_object_list = ticket_result_paginator.object_list
  182. ticket_result_restful_list = []
  183. for ticket_result_object in ticket_result_object_list:
  184. state_obj_flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(ticket_result_object.state_id)
  185. state_name = state_obj.name if state_obj_flag else '未知状态'
  186. flag, participant_info = cls.get_ticket_format_participant_info(ticket_result_object.id)
  187. flag, workflow_obj = workflow_base_service_ins.get_by_id(ticket_result_object.workflow_id)
  188. workflow_info_dict = dict(workflow_id=workflow_obj.id, workflow_name=workflow_obj.name)
  189. flag, creator_obj = account_base_service_ins.get_user_by_username(ticket_result_object.creator)
  190. if flag:
  191. flag, dept_dict_info = account_base_service_ins.get_user_dept_info(user_id=creator_obj.id)
  192. creator_info = dict(username=creator_obj.username, alias=creator_obj.alias,
  193. is_active=creator_obj.is_active, email=creator_obj.email, phone=creator_obj.phone,
  194. dept_info=dept_dict_info)
  195. else:
  196. creator_info = dict(username=ticket_result_object.creator, alias='', is_active=False, email='',
  197. phone='', dept_info={})
  198. ticket_format_obj = ticket_result_object.get_dict()
  199. state_obj_label = '{}'
  200. if state_obj_flag:
  201. state_obj_label = json.loads(state_obj.label)
  202. ticket_format_obj.update(dict(state=dict(state_id=ticket_result_object.state_id, state_name=state_name,
  203. state_label=state_obj_label),
  204. participant_info=participant_info, creator_info=creator_info,
  205. workflow_info=workflow_info_dict))
  206. ticket_result_restful_list.append(ticket_format_obj)
  207. return True, dict(ticket_result_restful_list=ticket_result_restful_list,
  208. paginator_info=dict(per_page=per_page, page=page, total=paginator.count))
  209. @classmethod
  210. @auto_log
  211. def new_ticket(cls, request_data_dict: dict, app_name: str='')->tuple:
  212. """
  213. 新建工单
  214. :param request_data_dict:
  215. :param app_name: 调用源app_name
  216. :return:
  217. """
  218. workflow_id = request_data_dict.get('workflow_id')
  219. transition_id = request_data_dict.get('transition_id')
  220. username = request_data_dict.get('username')
  221. parent_ticket_id = request_data_dict.get('parent_ticket_id', 0)
  222. parent_ticket_state_id = request_data_dict.get('parent_ticket_state_id', 0)
  223. suggestion = request_data_dict.get('suggestion', '')
  224. if not (workflow_id and transition_id and username):
  225. return False, u'参数不合法,请提供workflow_id,username,transition_id'
  226. request_field_arg_list = [key for key, value in request_data_dict.items()
  227. if (key not in ['workflow_id', 'suggestion', 'username'])]
  228. # 判断用户是否有权限新建该工单
  229. has_permission, msg = workflow_base_service_ins.check_new_permission(username, workflow_id)
  230. if not has_permission:
  231. return False, msg
  232. # 获取新建工单必填信息(根据工作流初始状态确定)
  233. flag, start_state = workflow_state_service_ins.get_workflow_start_state(workflow_id)
  234. if flag is False:
  235. return False, start_state
  236. flag, state_info_dict = cls.get_state_field_info(start_state.id)
  237. require_field_list = state_info_dict.get('require_field_list', []) # 必填字段
  238. update_field_list = state_info_dict.get('update_field_list', []) # 必填+可选字段,即需要保存值的字段
  239. # 校验是否所有必填字段都有提供,如果transition对应设置为不校验必填则直接通过
  240. flag, req_transition_obj = workflow_transition_service_ins.get_workflow_transition_by_id(transition_id)
  241. if req_transition_obj.field_require_check:
  242. for require_field in require_field_list:
  243. if require_field not in request_field_arg_list:
  244. return False, '此工单的必填字段为:{}'.format(','.join(require_field_list))
  245. flag, msg = cls.get_next_state_id_by_transition_and_ticket_info(0, request_data_dict)
  246. if flag:
  247. destination_state_id = msg.get('destination_state_id')
  248. else:
  249. return False, msg
  250. flag, destination_state = workflow_state_service_ins.get_workflow_state_by_id(destination_state_id)
  251. # 获取目标状态的信息
  252. flag, participant_info = cls.get_ticket_state_participant_info(destination_state_id,
  253. ticket_req_dict=request_data_dict)
  254. if not flag:
  255. return False, participant_info
  256. destination_participant_type_id = participant_info.get('destination_participant_type_id', 0)
  257. destination_participant = participant_info.get('destination_participant', '')
  258. multi_all_person = participant_info.get('multi_all_person', '{}') # 多人需要全部处理情况
  259. # 生成流水号
  260. flag, result = cls.gen_ticket_sn(app_name)
  261. if not flag:
  262. return False, result
  263. ticket_sn = result.get('ticket_sn')
  264. # 新增工单基础表数据
  265. if destination_state.type_id == constant_service_ins.STATE_TYPE_END:
  266. act_state_id = constant_service_ins.TICKET_ACT_STATE_FINISH
  267. elif destination_state.type_id == constant_service_ins.STATE_TYPE_START:
  268. act_state_id = constant_service_ins.TICKET_ACT_STATE_DRAFT
  269. else:
  270. act_state_id = constant_service_ins.TICKET_ACT_STATE_ONGOING
  271. flag, workflow_base_obj = workflow_base_service_ins.get_by_id(workflow_id)
  272. title_template = workflow_base_obj.title_template
  273. title = request_data_dict.get('title', '')
  274. import copy
  275. title_render_data = copy.deepcopy(request_data_dict)
  276. now_time = str(datetime.datetime.now())[:19]
  277. flag, user_info = account_base_service_ins.get_user_by_username(username)
  278. if flag:
  279. user_alias = user_info.alias
  280. else:
  281. user_alias = username
  282. title_render_data.update({'title': title, 'sn': ticket_sn, 'state_id': start_state.id, 'participant_info.participant_name': username,
  283. 'participant_info.alias': user_alias, 'workflow.workflow_name': workflow_base_obj.name,
  284. 'creator': username, 'gmt_created': now_time, 'gmt_modified': now_time, 'state.state_name': start_state.name})
  285. if title_template:
  286. title = title_template.format(**title_render_data)
  287. new_ticket_obj = TicketRecord(sn=ticket_sn, title=title, workflow_id=workflow_id,
  288. state_id=destination_state_id, parent_ticket_id=parent_ticket_id,
  289. parent_ticket_state_id=parent_ticket_state_id,
  290. participant=destination_participant,
  291. participant_type_id=destination_participant_type_id, relation=username,
  292. creator=username, act_state_id=act_state_id, multi_all_person=multi_all_person)
  293. new_ticket_obj.save()
  294. # 更新工单关系人
  295. flag, result = cls.get_ticket_dest_relation(destination_participant_type_id, destination_participant)
  296. if flag is True:
  297. cls.update_ticket_relation(new_ticket_obj.id, result.get('add_relation'), ticket_creator=username)
  298. # 新增自定义字段,只保存required_field
  299. request_data_dict_allow = {}
  300. for key, value in request_data_dict.items():
  301. if key in update_field_list:
  302. request_data_dict_allow[key] = value
  303. update_ticket_custom_field_result, msg = cls.update_ticket_custom_field(new_ticket_obj.id,
  304. request_data_dict_allow)
  305. if not update_ticket_custom_field_result:
  306. return False, msg
  307. # 新增流转记录,记录流转时工单所有字段的值
  308. flag, result = cls.get_ticket_all_field_value_json(new_ticket_obj.id)
  309. if flag is False:
  310. return False, result
  311. all_ticket_data_json = result.get('all_field_value_json')
  312. new_ticket_flow_log_dict = dict(ticket_id=new_ticket_obj.id, transition_id=transition_id,
  313. suggestion=suggestion,
  314. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  315. participant=username, state_id=start_state.id, ticket_data=all_ticket_data_json)
  316. add_ticket_flow_log_result, msg = cls.add_ticket_flow_log(new_ticket_flow_log_dict)
  317. if not add_ticket_flow_log_result:
  318. return False, msg
  319. # 通知消息
  320. from tasks import send_ticket_notice
  321. send_ticket_notice.apply_async(args=[new_ticket_obj.id], queue='loonflow')
  322. # 如果下个状态为脚本处理,则开始执行脚本
  323. if destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROBOT:
  324. from tasks import run_flow_task # 放在文件开头会存在循环引用
  325. run_flow_task.apply_async(args=[new_ticket_obj.id, destination_participant, destination_state_id],
  326. queue='loonflow')
  327. # 如果下个状态是hook,开始触发hook
  328. if destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_HOOK:
  329. from tasks import flow_hook_task # 放在文件开头会存在循环引用
  330. flow_hook_task.apply_async(args=[new_ticket_obj.id], queue='loonflow')
  331. # 定时器处理逻辑
  332. cls.handle_timer_transition(new_ticket_obj.id, destination_state_id)
  333. # 父工单逻辑处理
  334. if destination_state.type_id == constant_service_ins.STATE_TYPE_END and new_ticket_obj.parent_ticket_id \
  335. and new_ticket_obj.parent_ticket_state_id:
  336. # 如果存在父工单,判断是否该父工单的下属子工单都已经结束状态,如果都是结束状态则自动流转父工单到下个状态
  337. filter_params = dict(
  338. parent_ticket_id=new_ticket_obj.parent_ticket_id,
  339. parent_ticket_state_id=new_ticket_obj.parent_ticket_state_id,
  340. is_deleted=0
  341. )
  342. other_sub_ticket_queryset = TicketRecord.objects.filter(**filter_params).all()
  343. # 所有子工单使用相同的工作流,所以state都一样,检测是否都是ticket_obj.state_id即可判断是否都是结束状态
  344. other_sub_ticket_state_id_list = [other_sub_ticket.state_id
  345. for other_sub_ticket in other_sub_ticket_queryset]
  346. flag, result = workflow_state_service_ins.get_states_info_by_state_id_list(other_sub_ticket_state_id_list)
  347. if flag:
  348. sub_ticket_state_type_list = []
  349. for key, value in result.items():
  350. sub_ticket_state_type_list.append(value.get('type_id'))
  351. list_set = set(sub_ticket_state_type_list)
  352. if list_set == {constant_service_ins.STATE_TYPE_END}:
  353. parent_ticket_obj = TicketRecord.objects.filter(id=new_ticket_obj.parent_ticket_id, is_deleted=0) \
  354. .first()
  355. parent_ticket_state_id = parent_ticket_obj.state_id
  356. flag, parent_ticket_transition_queryset = workflow_transition_service_ins \
  357. .get_state_transition_queryset(parent_ticket_state_id)
  358. # 含有子工单的工单状态只支持单路径流转到下个状态
  359. parent_ticket_transition_id = parent_ticket_transition_queryset[0].id
  360. cls.handle_ticket(parent_ticket_obj.id, dict(transition_id=parent_ticket_transition_id,
  361. username='loonrobot',
  362. suggestion='所有子工单处理完毕,自动流转'))
  363. return True, dict(new_ticket_id=new_ticket_obj.id)
  364. @classmethod
  365. @auto_log
  366. def gen_ticket_sn(cls, app_name: str='')->tuple:
  367. redis_conn = redis.Redis(connection_pool=POOL)
  368. ticket_day_count_key = 'ticket_day_count_{}'.format(str(datetime.datetime.now())[:10])
  369. try:
  370. ticket_day_count = redis_conn.get(ticket_day_count_key)
  371. except redis.exceptions.ConnectionError:
  372. return False, 'Redis连接失败,请确认Redis已启动并配置正确'
  373. except Exception as e:
  374. raise Exception(e.__str__())
  375. if ticket_day_count is not None:
  376. new_ticket_day_count = redis_conn.incr(ticket_day_count_key)
  377. else:
  378. # 查询数据库中个数
  379. # 今天和明天
  380. today = str(datetime.datetime.now())[:10] + " 00:00:00"
  381. next_day = str(datetime.datetime.now() + datetime.timedelta(days=1))[:10] + " 00:00:00"
  382. # 包括is_deleted=1的数据
  383. ticket_day_count = TicketRecord.objects.filter(gmt_created__gte=today, gmt_created__lt=next_day).count()
  384. new_ticket_day_count = int(ticket_day_count) + 1
  385. redis_conn.set(ticket_day_count_key, new_ticket_day_count, 86400)
  386. now_day = datetime.datetime.now()
  387. if not app_name:
  388. sn_prefix = 'loonflow'
  389. else:
  390. if app_name == 'loonflow':
  391. sn_prefix = 'loonflow'
  392. else:
  393. flag, result = account_base_service_ins.get_token_by_app_name(app_name)
  394. if flag is False:
  395. return False, result
  396. sn_prefix = result.ticket_sn_prefix
  397. zone_info = ''
  398. if settings.DEPLOY_ZONE:
  399. # for multi computer room deploy and use separate redis server
  400. zone_info = '{}_'.format(settings.DEPLOY_ZONE)
  401. return True, dict(ticket_sn='%s_%s%04d%02d%02d%04d' % (sn_prefix, zone_info, now_day.year, now_day.month,
  402. now_day.day, new_ticket_day_count))
  403. @classmethod
  404. @auto_log
  405. def get_ticket_field_value(cls, ticket_id: int, field_key: str)->tuple:
  406. """
  407. get ticket field's value, include base filed and custom field
  408. :param ticket_id:
  409. :param field_key:
  410. :return:
  411. """
  412. if field_key in constant_service_ins.TICKET_BASE_FIELD_LIST:
  413. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  414. ticket_obj_dict = ticket_obj.get_dict()
  415. value = ticket_obj_dict.get(field_key)
  416. else:
  417. flag, result = cls.get_ticket_custom_field_value(ticket_id, field_key)
  418. if flag is False:
  419. return False, result
  420. value = result.get('value')
  421. return True, dict(value=value)
  422. @classmethod
  423. @auto_log
  424. def get_ticket_format_custom_field_key_dict(cls, ticket_id: int)->tuple:
  425. """
  426. get ticket custom field attribute to dict format
  427. :param ticket_id:
  428. :return:
  429. """
  430. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  431. custom_field_queryset = CustomField.objects.filter(is_deleted=0, workflow_id=ticket_obj.workflow_id).all()
  432. format_field_key_dict = {}
  433. for custom_field in custom_field_queryset:
  434. format_field_key_dict[custom_field.field_key] = dict(field_type_id=custom_field.field_type_id,
  435. name=custom_field.field_name,
  436. bool_field_display=custom_field.boolean_field_display,
  437. field_choice=custom_field.field_choice,
  438. field_from='custom')
  439. return True, format_field_key_dict
  440. @classmethod
  441. @auto_log
  442. def get_ticket_custom_field_value(cls, ticket_id: int, field_key: str)->tuple:
  443. """
  444. get ticket custom field value
  445. :param ticket_id:
  446. :param field_key:
  447. :return:
  448. """
  449. flag, result = cls.get_ticket_format_custom_field_key_dict(ticket_id)
  450. if flag is False:
  451. return False, result
  452. field_type_id = result[field_key]['field_type_id']
  453. ticket_custom_field_obj = TicketCustomField.objects.filter(field_key=field_key, ticket_id=ticket_id,
  454. is_deleted=0).first()
  455. if not ticket_custom_field_obj:
  456. # has not been assignment
  457. value = None
  458. else:
  459. value_dict = ticket_custom_field_obj.get_dict()
  460. value_enum = constant_service_ins.FIELD_VALUE_ENUM
  461. value = value_dict.get(value_enum[field_type_id])
  462. return True, dict(value=value)
  463. @classmethod
  464. @auto_log
  465. def get_ticket_field_name(cls, ticket_id: int, field_key: str)->tuple:
  466. """
  467. get ticket field's name by field_key
  468. :param ticket_id:
  469. :param field_key:
  470. :return:
  471. """
  472. if field_key in constant_service_ins.TICKET_BASE_FIELD_LIST:
  473. return True, dict(field_name=field_key)
  474. else:
  475. flag, result = cls.get_ticket_custom_field_name(ticket_id, field_key)
  476. if flag is False:
  477. return False, result
  478. return True, dict(field_name=result.get('field_name'))
  479. @classmethod
  480. @auto_log
  481. def get_ticket_custom_field_name(cls, ticket_id: int, field_key: str)->tuple:
  482. """
  483. get ticket custom field's field_name
  484. :param ticket_id:
  485. :param field_key:
  486. :return:
  487. """
  488. flag, result = cls.get_ticket_format_custom_field_key_dict(ticket_id)
  489. if flag is False:
  490. return False, result
  491. field_name = result[field_key]['field_name']
  492. return True, dict(field_name=field_name)
  493. @classmethod
  494. @auto_log
  495. def update_ticket_custom_field(cls, ticket_id: int, update_dict: dict)->tuple:
  496. """
  497. update ticket's custom fields's value(create or update)
  498. :param ticket_id:
  499. :param update_dict:
  500. :return:
  501. """
  502. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  503. flag, format_custom_field_dict = workflow_custom_field_service_ins\
  504. .get_workflow_custom_field(ticket_obj.workflow_id)
  505. if flag is False:
  506. return False, format_custom_field_dict
  507. format_custom_field_dict = format_custom_field_dict
  508. custom_field_key_list = [key for key, value in format_custom_field_dict.items()]
  509. # 因为工单的自定义字段不会太多,且有可能是新增有可能是更新, 所以直接遍历处理
  510. for key, value in update_dict.items():
  511. if key in custom_field_key_list:
  512. # 判断是否存在,如果存在则更新,如果不存在则新增
  513. ticket_custom_field_queryset = TicketCustomField.objects.filter(
  514. ticket_id=ticket_id, field_key=key, is_deleted=0)
  515. field_type_id = format_custom_field_dict[key]['field_type_id']
  516. if update_dict.get(key) is None:
  517. # 值为None。说明此字段为可选,且用户未填写或者清空了该字段,需要清空字段
  518. if ticket_custom_field_queryset:
  519. # 已经存在,需要删除
  520. ticket_custom_field_queryset.update(is_deleted=1)
  521. else:
  522. # 不存在的,直接忽略
  523. pass
  524. else:
  525. value_enum = constant_service_ins.FIELD_VALUE_ENUM
  526. if ticket_custom_field_queryset:
  527. ticket_custom_field_queryset.update(**{value_enum.get(field_type_id): update_dict.get(key)})
  528. elif not ticket_custom_field_queryset:
  529. new_dict = {
  530. 'name': format_custom_field_dict[key]['field_name'],
  531. 'ticket_id': ticket_id,
  532. 'field_key': key,
  533. 'field_type_id': field_type_id,
  534. value_enum[field_type_id]: update_dict.get(key)
  535. }
  536. new_ticket_custom_field_record = TicketCustomField(**new_dict)
  537. new_ticket_custom_field_record.save()
  538. return True, ''
  539. @classmethod
  540. @auto_log
  541. def update_ticket_field_value(cls, ticket_id: int, update_dict: dict)-> tuple:
  542. """
  543. update ticket field's value
  544. :param ticket_id:
  545. :param update_dict:
  546. :return:
  547. """
  548. base_field_dict = {}
  549. for key, value in update_dict.items():
  550. if key in constant_service_ins.TICKET_BASE_FIELD_LIST:
  551. base_field_dict[key] = value
  552. # ticket base field
  553. if base_field_dict:
  554. TicketRecord.objects.filter(id=ticket_id, is_deleted=0).update(**base_field_dict)
  555. # custom field
  556. cls.update_ticket_custom_field(ticket_id, update_dict)
  557. return True, ''
  558. @classmethod
  559. @auto_log
  560. def add_ticket_flow_log(cls, kwargs: dict)->tuple:
  561. """
  562. add ticket flow record
  563. :param kwargs:
  564. :return:
  565. """
  566. # in some mysql version's default config, string while be structure if the length is greater than defined
  567. suggestion = kwargs.get('suggestion', '') if kwargs.get('suggestion', '') else ''
  568. if len(suggestion) > 1000:
  569. kwargs['suggestion'] = '{}...(be truncated because More than 1000)'\
  570. .format(kwargs.get('suggestion', '')[:960])
  571. kwargs['suggestion'] = suggestion
  572. if not kwargs.get('creator'):
  573. kwargs['creator'] = kwargs.get('participant', '')
  574. new_ticket_flow_log = TicketFlowLog(**kwargs)
  575. new_ticket_flow_log.save()
  576. return True, dict(new_ticket_flow_log_id=new_ticket_flow_log.id)
  577. @classmethod
  578. @auto_log
  579. def get_ticket_detail(cls, ticket_id: int, username: str)-> tuple:
  580. """
  581. get ticket's detail info, According to the current state and username.
  582. if user only has reade permission, the response field accord to workflow config, otherwise state config
  583. :param ticket_id:
  584. :param username:
  585. :return:
  586. """
  587. flag, result = cls.ticket_handle_permission_check(ticket_id, username)
  588. if flag is False or not result.get('permission'):
  589. view_permission, msg = cls.ticket_view_permission_check(ticket_id, username)
  590. if not view_permission:
  591. return False, msg
  592. handle_permission = False
  593. else:
  594. handle_permission = True
  595. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  596. flag, result = cls.get_ticket_base_field_list(ticket_id)
  597. field_list = result.get('field_list') if flag else []
  598. new_field_list = []
  599. if handle_permission:
  600. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(ticket_obj.state_id)
  601. if flag:
  602. state_field_str = state_obj.state_field_str
  603. state_field_dict = json.loads(state_field_str)
  604. state_field_key_list = state_field_dict.keys()
  605. for field in field_list:
  606. if field['field_key'] in state_field_key_list:
  607. field['field_attribute'] = state_field_dict[field['field_key']]
  608. new_field_list.append(field)
  609. else:
  610. # only read permission
  611. flag, workflow_obj = workflow_base_service_ins.get_by_id(workflow_id=ticket_obj.workflow_id)
  612. display_form_field_list = json.loads(workflow_obj.display_form_str) if workflow_obj.display_form_str else []
  613. for field in field_list:
  614. if field['field_key'] in display_form_field_list:
  615. new_field_list.append(field)
  616. # order by field's order id
  617. new_field_list = sorted(new_field_list, key=lambda r: r['order_id'])
  618. flag, creator_obj = account_base_service_ins.get_user_by_username(ticket_obj.creator)
  619. if flag:
  620. flag, dept_dict_info = account_base_service_ins.get_user_dept_info(user_id=creator_obj.id)
  621. creator_info = dict(username=creator_obj.username, alias=creator_obj.alias,
  622. is_active=creator_obj.is_active, email=creator_obj.email,
  623. phone=creator_obj.phone, dept_info=dept_dict_info)
  624. else:
  625. creator_info = dict(username=ticket_obj.creator, alias='', is_active=False, email='', phone='',
  626. dept_info={})
  627. # 当前状态信息
  628. flag, result = workflow_state_service_ins.get_workflow_state_by_id(ticket_obj.state_id)
  629. if flag:
  630. state_info = result.get_dict()
  631. if state_info['participant_type_id'] == constant_service_ins.PARTICIPANT_TYPE_HOOK:
  632. state_info['participant'] = 'hook'
  633. state_info['state_field_str'] = json.loads(state_info['state_field_str'])
  634. state_info['label'] = json.loads(state_info['label'])
  635. else:
  636. state_info = dict(id=ticket_obj.state_id, name='--状态已被删除--')
  637. ticket_result_dict = ticket_obj.get_dict()
  638. ticket_result_dict.update(dict(field_list=new_field_list, creator_info=creator_info, state_info=state_info))
  639. return True, ticket_result_dict
  640. @classmethod
  641. @auto_log
  642. def get_ticket_base_field_list(cls, ticket_id: int)->tuple:
  643. """
  644. get ticket base field info list
  645. :param ticket_id:
  646. :return:
  647. """
  648. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  649. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(ticket_obj.state_id)
  650. if flag is False:
  651. return False, state_obj
  652. state_name = state_obj.name
  653. # base field and attribute
  654. field_list = []
  655. flag, participant_info_dict = cls.get_ticket_format_participant_info(ticket_id)
  656. if flag is False:
  657. return False, participant_info_dict
  658. flag, workflow_obj = workflow_base_service_ins.get_by_id(ticket_obj.workflow_id)
  659. if flag is False:
  660. return False, workflow_obj
  661. workflow_name = workflow_obj.name
  662. field_list.append(dict(field_key='sn', field_name=u'流水号', field_value=ticket_obj.sn, order_id=10,
  663. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  664. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO,
  665. description='', field_choice={}, boolean_field_display={}, default_value=None,
  666. field_template='', label={}, placeholder=''))
  667. field_list.append(dict(field_key='title', field_name=u'标题', field_value=ticket_obj.title, order_id=20,
  668. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  669. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO,description='',
  670. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  671. label={}, placeholder=''))
  672. field_list.append(dict(field_key='state_id', field_name=u'状态id', field_value=ticket_obj.state_id, order_id=40,
  673. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  674. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  675. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  676. label={}, placeholder=''))
  677. field_list.append(dict(field_key='participant_info.participant_name', field_name=u'当前处理人',
  678. field_value=participant_info_dict['participant_name'], order_id=50,
  679. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  680. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  681. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  682. label={}, placeholder=''))
  683. field_list.append(dict(field_key='participant_info.participant_alias', field_name=u'当前处理人',
  684. field_value=participant_info_dict['participant_alias'], order_id=55,
  685. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  686. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO,
  687. description='', field_choice={}, boolean_field_display={},
  688. default_value=None, field_template='', label={}, placeholder=''))
  689. field_list.append(dict(field_key='workflow.workflow_name', field_name=u'工作流名称', field_value=workflow_name,
  690. order_id=60, field_type_id=constant_service_ins.FIELD_TYPE_STR,
  691. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  692. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  693. label={}, placeholder=''))
  694. field_list.append(dict(field_key='creator', field_name=u'创建人', field_value=ticket_obj.creator, order_id=80,
  695. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  696. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  697. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  698. label={}, placeholder=''))
  699. field_list.append(dict(field_key='gmt_created', field_name=u'创建时间',
  700. field_value=str(ticket_obj.gmt_created)[:19], order_id=100,
  701. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  702. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  703. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  704. label={}, placeholder=''))
  705. field_list.append(dict(field_key='gmt_modified', field_name=u'更新时间',
  706. field_value=str(ticket_obj.gmt_modified)[:19], order_id=120,
  707. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  708. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  709. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  710. label={}, placeholder=''))
  711. field_list.append(dict(field_key='state.state_name', field_name=u'状态名', field_value=state_name, order_id=41,
  712. field_type_id=constant_service_ins.FIELD_TYPE_STR,
  713. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO, description='',
  714. field_choice={}, boolean_field_display={}, default_value=None, field_template='',
  715. label={}, placeholder=''))
  716. # ticket's all custom field
  717. flag, custom_field_dict = workflow_custom_field_service_ins.get_workflow_custom_field(ticket_obj.workflow_id)
  718. custom_field_key_list = [key for key, value in custom_field_dict.items()]
  719. ticket_custom_field_objs = TicketCustomField.objects.filter(ticket_id=ticket_id,
  720. field_key__in=custom_field_key_list,
  721. is_deleted=0).all()
  722. key_value_dict = {}
  723. for ticket_custom_field_obj in ticket_custom_field_objs:
  724. key_value_dict[ticket_custom_field_obj.field_key] = ticket_custom_field_obj
  725. for key, value in custom_field_dict.items():
  726. field_type_id = value['field_type_id']
  727. field_value_obj = key_value_dict.get(key)
  728. if not field_value_obj:
  729. field_value = None
  730. else:
  731. value_enum = constant_service_ins.FIELD_VALUE_ENUM
  732. field_value = field_value_obj.get_dict().get(value_enum.get(field_type_id))
  733. boolean_field_display = json.loads(
  734. custom_field_dict[key]['boolean_field_display']) if custom_field_dict[key]['boolean_field_display'] \
  735. else {} # 之前model允许为空了,为了兼容先这么写
  736. field_list.append(dict(field_key=key, field_name=custom_field_dict[key]['field_name'],
  737. field_value=field_value, order_id=custom_field_dict[key]['order_id'],
  738. field_type_id=custom_field_dict[key]['field_type_id'],
  739. field_attribute=constant_service_ins.FIELD_ATTRIBUTE_RO,
  740. default_value=custom_field_dict[key]['default_value'],
  741. description=custom_field_dict[key]['description'],
  742. field_template=custom_field_dict[key]['field_template'],
  743. boolean_field_display=boolean_field_display,
  744. field_choice=json.loads(custom_field_dict[key]['field_choice']),
  745. label=json.loads(custom_field_dict[key]['label']),
  746. placeholder=custom_field_dict[key]['placeholder']
  747. ))
  748. return True, dict(field_list=field_list)
  749. @classmethod
  750. @auto_log
  751. def get_ticket_format_participant_info(cls, ticket_id: int)->tuple:
  752. """
  753. get ticket's format participant_info(include role_name, department_name and so on )
  754. :param ticket_id:
  755. :return:
  756. """
  757. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  758. participant = ticket_obj.participant
  759. participant_name = ticket_obj.participant
  760. participant_type_id = ticket_obj.participant_type_id
  761. participant_type_name = ''
  762. participant_alias = ''
  763. if participant_type_id == constant_service_ins.PARTICIPANT_TYPE_PERSONAL:
  764. participant_type_name = '个人'
  765. # participant_user_obj, msg = AccountBaseService.get_user_by_username(participant)
  766. flag, participant_user_obj = account_base_service_ins.get_user_by_username(participant)
  767. if flag:
  768. participant_alias = participant_user_obj.alias
  769. else:
  770. participant_alias = participant
  771. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_MULTI:
  772. participant_type_name = '多人'
  773. participant_name_list = participant_name.split(',')
  774. participant_map = {"name": set(), "alias": set()}
  775. flag, participant_user_objs = account_base_service_ins.get_user_list_by_usernames(participant_name_list)
  776. if flag:
  777. for participant_user in participant_user_objs:
  778. participant_map["name"].add(participant_user.username)
  779. participant_map["alias"].add(participant_user.alias)
  780. participant_map["alias"].update(
  781. set(participant_name_list) - participant_map["name"]
  782. )
  783. participant_alias = ','.join(participant_map["alias"])
  784. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_DEPT:
  785. participant_type_name = '部门'
  786. flag, dept_obj = account_base_service_ins.get_dept_by_id(int(ticket_obj.participant))
  787. if flag is False:
  788. return False, dept_obj
  789. if not dept_obj:
  790. return False, 'dept_id:{} is not existed or has been deleted'.format(ticket_obj.participant)
  791. participant_name = dept_obj.name
  792. participant_alias = participant_name
  793. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROLE:
  794. participant_type_name = '角色'
  795. flag, role_obj = account_base_service_ins.get_role_by_id(int(ticket_obj.participant))
  796. if flag is False or (not role_obj):
  797. return False, 'role is not existed or has been deleted'
  798. participant_name = role_obj.name
  799. participant_alias = participant_name
  800. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROBOT:
  801. # 脚本类型参数与人是脚本记录的id
  802. from apps.workflow.models import WorkflowScript
  803. script_obj = WorkflowScript.objects.filter(id=int(participant), is_deleted=0).first()
  804. if script_obj:
  805. participant_name = participant
  806. participant_alias = '脚本:{}'.format(script_obj.name)
  807. if json.loads(ticket_obj.multi_all_person):
  808. participant_type_name = '多人且全部处理'
  809. # 从multi_all_person中获取处理人信息
  810. multi_all_person_dict = json.loads(ticket_obj.multi_all_person)
  811. participant_alias0_list = []
  812. for key, value in multi_all_person_dict.items():
  813. flag, participant_user_obj = account_base_service_ins.get_user_by_username(key)
  814. if not flag:
  815. participant_alias0 = key
  816. else:
  817. participant_alias0 = participant_user_obj.alias
  818. if value:
  819. participant_alias0_list.append(
  820. '{}({})已处理:{}'.format(participant_alias0, key, value.get('transition_name')))
  821. else:
  822. participant_alias0_list.append(
  823. '{}({})未处理:{}'.format(participant_alias0, key, value.get('transition_name')))
  824. participant_alias = ';'.join(participant_alias0_list)
  825. # 工单基础表中不存在参与人为其他类型的情况
  826. return True, dict(participant=participant, participant_name=participant_name,
  827. participant_type_id=participant_type_id, participant_type_name=participant_type_name,
  828. participant_alias=participant_alias)
  829. @classmethod
  830. @auto_log
  831. def ticket_handle_permission_check(cls, ticket_id: int, username: str, by_timer: bool=False, by_task: bool=False,
  832. by_hook: bool=False)->tuple:
  833. """
  834. handle permission check
  835. :param ticket_id:
  836. :param username:
  837. :param by_timer: is timer or not
  838. :param by_task: is by script or not
  839. :param by_hook: is by hook or not
  840. :return:
  841. """
  842. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  843. if not ticket_obj:
  844. return False, '工单不存在或已被删除'
  845. ticket_state_id = ticket_obj.state_id
  846. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(ticket_state_id)
  847. if not flag:
  848. return True, dict(permission=False, msg='工单当前状态id不存在或已被删除')
  849. flag, transition_queryset = workflow_transition_service_ins.get_state_transition_queryset(ticket_state_id)
  850. if flag is False:
  851. return False, transition_queryset
  852. if not transition_queryset:
  853. return True, dict(permission=False, msg='工单当前状态无需操作')
  854. if by_timer and username == 'loonrobot':
  855. # by time
  856. return True, dict(permission=True, need_accept=False, in_add_node=False, msg='by timer,release permissions')
  857. if by_task and username == 'loonrobot':
  858. # by script
  859. return True, dict(permission=True, need_accept=False, in_add_node=False,
  860. msg='by script,release permissions')
  861. if by_hook and username == 'loonrobot':
  862. # by hook
  863. return True, dict(permission=True, need_accept=False, in_add_node=False, msg='by hook,release permissions')
  864. participant_type_id = ticket_obj.participant_type_id
  865. participant = ticket_obj.participant
  866. current_participant_count = 1 # 当前处理人个数,用于当处理人大于1时 可能需要先接单再处理
  867. if participant_type_id == constant_service_ins.PARTICIPANT_TYPE_PERSONAL:
  868. if username != participant:
  869. return True, dict(permission=False, need_accept=False, in_add_node=False,
  870. msg='not current participant, no permission')
  871. # return None, '非当前处理人,无权处理'
  872. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_MULTI:
  873. if username not in participant.split(','):
  874. return True, dict(permission=False, need_accept=False, in_add_node=False,
  875. msg='not crrent participant, no permission')
  876. # return None, '非当前处理人,无权处理'
  877. current_participant_count = len(participant.split(','))
  878. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_DEPT:
  879. flag, dept_user_list = account_base_service_ins.get_dept_username_list(participant)
  880. if flag is False:
  881. return flag, dept_user_list
  882. if username not in dept_user_list:
  883. # return None, '非当前处理人,无权处理'
  884. return True, dict(permission=False, need_accept=False, in_add_node=False,
  885. msg='not current participant, no permission')
  886. current_participant_count = len(dept_user_list)
  887. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROLE:
  888. flag, role_user_list = account_base_service_ins.get_role_username_list(int(participant))
  889. if not flag:
  890. return False, role_user_list
  891. if username not in role_user_list:
  892. return True, dict(permission=False, need_accept=False, in_add_node=False,
  893. msg='not crrent participant, no permission')
  894. # return None, '非当前处理人,无权处理'
  895. current_participant_count = len(role_user_list)
  896. else:
  897. return True, dict(permission=False, need_accept=False, in_add_node=False,
  898. msg='not crrent participant, no permission')
  899. # return None, '非当前处理人,无权处理'
  900. # PARTICIPANT_TYPE_VARIABLE, PARTICIPANT_TYPE_FIELD, PARTICIPANT_TYPE_PARENT_FIELD类型会在流转时保存为实际的处理人
  901. if current_participant_count > 1 and \
  902. state_obj.distribute_type_id == constant_service_ins.STATE_DISTRIBUTE_TYPE_ACTIVE:
  903. need_accept = True
  904. else:
  905. need_accept = False
  906. if ticket_obj.in_add_node:
  907. in_add_node = True
  908. else:
  909. in_add_node = False
  910. return True, dict(permission=True, need_accept=need_accept, in_add_node=in_add_node)
  911. @classmethod
  912. @auto_log
  913. def ticket_view_permission_check(cls, ticket_id: int, username: str)-> tuple:
  914. """
  915. check user whether have view permission if open permission check in workflow config, otherwise decide by ticket releation
  916. 校验用户是否有工单的查看权限:先查询对应的工作流是否校验查看权限, 如果不校验直接允许,如果校验需要判断用户是否属于工单的关系人
  917. :param ticket_id:
  918. :param username:
  919. :return:
  920. """
  921. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  922. if not ticket_obj:
  923. return False, 'ticket is not existed or has been deleted'
  924. flag, workflow_obj = workflow_base_service_ins.get_by_id(ticket_obj.workflow_id)
  925. if flag is False:
  926. return False, workflow_obj
  927. if not workflow_obj.view_permission_check:
  928. return True, "this ticket's workflow config did not open view permission check"
  929. else:
  930. flag, user_obj = account_base_service_ins.get_user_by_username(username)
  931. if flag is False:
  932. return False, 'user is not existed or has been deleted'
  933. else:
  934. if user_obj.type_id == constant_service_ins.ACCOUNT_TYPE_SUPER_ADMIN:
  935. return True, 'admin has all ticket view permission'
  936. if username in ticket_obj.relation.split(','):
  937. return True, 'user is relation about this ticket, has view permission'
  938. else:
  939. return False, 'user is not relation about this ticket and the workflow open view permission check'
  940. @classmethod
  941. @auto_log
  942. def get_ticket_transition(cls, ticket_id: int, username: str)->tuple:
  943. """
  944. 获取用户针对工单当前可以做的操作:处理权限校验、可以做的操作
  945. get ticket's action about some one
  946. :param ticket_id:
  947. :param username:
  948. :return:
  949. """
  950. flag, result = cls.ticket_handle_permission_check(ticket_id, username)
  951. if flag is False:
  952. return False, result
  953. if not result.get('permission'):
  954. return True, dict(transition_dict_list=[], msg=result.get('msg'))
  955. ticket_obj = TicketRecord.objects.filter(id=ticket_id).first()
  956. if ticket_obj.in_add_node:
  957. # add node state, just allow 'finish' action, after finish the ticket's participant will be set add_node_man
  958. transition_dict_list = [dict(transition_id=0, transition_name='完成', field_require_check=False,
  959. is_accept=False, in_add_node=True, alert_enable=False, alert_text='',
  960. attribute_type_id=constant_service_ins.TRANSITION_ATTRIBUTE_TYPE_OTHER)]
  961. return True, dict(transition_dict_list=transition_dict_list)
  962. if result.get('need_accept'):
  963. transition_dict_list = [dict(transition_id=0, transition_name='接单', field_require_check=False,
  964. is_accept=True, in_add_node=False, alert_enable=False, alert_text='',
  965. attribute_type_id=constant_service_ins.TRANSITION_ATTRIBUTE_TYPE_OTHER)]
  966. return True, dict(transition_dict_list=transition_dict_list)
  967. flag, transition_queryset = workflow_transition_service_ins.get_state_transition_queryset(ticket_obj.state_id)
  968. transition_dict_list = []
  969. for transition in transition_queryset:
  970. transition_dict = dict(transition_id=transition.id, transition_name=transition.name,
  971. field_require_check=transition.field_require_check, is_accept=False,
  972. in_add_node=False, alert_enable=transition.alert_enable,
  973. alert_text=transition.alert_text, attribute_type_id=transition.attribute_type_id)
  974. transition_dict_list.append(transition_dict)
  975. return True, dict(transition_dict_list=transition_dict_list)
  976. @classmethod
  977. @auto_log
  978. def handle_ticket(cls, ticket_id: int, request_data_dict: dict, by_timer: bool=False, by_task: bool=False,
  979. by_hook: bool=False):
  980. """
  981. 处理工单:校验必填参数,获取当前状态必填字段,更新工单基础字段,更新工单自定义字段, 更新工单流转记录,执行必要的脚本,通知消息
  982. 此处逻辑和新建工单有较多重复,下个版本会拆出来
  983. handle ticket, include params check,update base field, update custom field, update ticket flowlog,
  984. run script, send notice...
  985. :param ticket_id:
  986. :param request_data_dict:
  987. :param by_timer: by timer transition
  988. :param by_task: by script task transition
  989. :param by_hook: by hook transition
  990. :return:
  991. """
  992. transition_id = request_data_dict.get('transition_id', '')
  993. username = request_data_dict.get('username', '')
  994. suggestion = request_data_dict.get('suggestion', '')
  995. if not (transition_id and username):
  996. return False, '参数不合法,请提供username,transition_id'
  997. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=False).first()
  998. source_ticket_state_id = ticket_obj.state_id
  999. if not ticket_obj:
  1000. return False, '工单不存在或已被删除'
  1001. # 判断用户是否有权限处理该工单
  1002. flag, result = cls.ticket_handle_permission_check(ticket_id, username, by_timer, by_task, by_hook)
  1003. if flag is False:
  1004. return False, result
  1005. if result.get('permission') is False:
  1006. return False, result.get('msg')
  1007. if result.get('need_accept'):
  1008. return False, '需要先接单再处理'
  1009. if result.get('in_add_node'):
  1010. return False, '工单当前处于加签中,只允许加签完成操作'
  1011. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(ticket_obj.state_id)
  1012. if flag is False:
  1013. return False, state_obj
  1014. # 获取初始状态必填字段 及允许更新的字段
  1015. flag, state_info_dict = cls.get_state_field_info(state_obj.id)
  1016. require_field_list = state_info_dict.get('require_field_list', [])
  1017. update_field_list = state_info_dict.get('update_field_list', [])
  1018. # 校验是否所有必填字段都有提供,如果transition_id对应设置为不校验必填则直接通过
  1019. flag, req_transition_obj = workflow_transition_service_ins.get_workflow_transition_by_id(transition_id)
  1020. if req_transition_obj.field_require_check:
  1021. request_field_arg_list = [key for key, value in request_data_dict.items()
  1022. if (key not in ['workflow_id', 'suggestion', 'username'])]
  1023. for require_field in require_field_list:
  1024. if require_field not in request_field_arg_list:
  1025. return False, '此工单的必填字段为:{}'.format(','.join(require_field_list))
  1026. flag, msg = cls.get_next_state_id_by_transition_and_ticket_info(ticket_id, request_data_dict)
  1027. if flag:
  1028. destination_state_id = msg.get('destination_state_id')
  1029. else:
  1030. return False, msg
  1031. flag, destination_state = workflow_state_service_ins.get_workflow_state_by_id(destination_state_id)
  1032. # 判断当前处理人类型是否为全部处理,如果处理类型为全部处理(根据json.loads(ticket_obj.multi_all_person)来判断),且有人未处理,则工单状态不变,只记录处理过程
  1033. if json.loads(ticket_obj.multi_all_person):
  1034. multi_all_person = ticket_obj.multi_all_person
  1035. multi_all_person_dict = json.loads(multi_all_person)
  1036. flag, result = common_service_ins.get_dict_blank_or_false_value_key_list(multi_all_person_dict)
  1037. if flag and result.get('result_list'):
  1038. multi_all_person_dict[username] = dict(transition_id=transition_id,
  1039. transition_name=req_transition_obj.name)
  1040. has_all_same_value, msg = common_service_ins.check_dict_has_all_same_value(multi_all_person_dict)
  1041. if has_all_same_value:
  1042. # 所有人处理的transition都一致,则工单进入下个状态
  1043. flag, participant_info = cls.get_ticket_state_participant_info(destination_state_id, ticket_id,
  1044. ticket_req_dict=request_data_dict)
  1045. if not flag:
  1046. return False, participant_info
  1047. destination_participant_type_id = participant_info.get('destination_participant_type_id', 0)
  1048. destination_participant = participant_info.get('destination_participant', '')
  1049. multi_all_person = '{}'
  1050. else:
  1051. # 处理人没有没有全部处理完成或者处理动作不一致
  1052. destination_participant_type_id = ticket_obj.participant_type_id
  1053. flag, result = common_service_ins.get_dict_blank_or_false_value_key_list(multi_all_person_dict)
  1054. destination_participant = ','.join(result.get('result_list'))
  1055. destination_state_id = ticket_obj.state_id # 保持原状态
  1056. flag, destination_state = workflow_state_service_ins.get_workflow_state_by_id(destination_state_id)
  1057. multi_all_person = json.dumps(multi_all_person_dict)
  1058. else:
  1059. # 当前处理人类型非全部处理
  1060. # flag, destination_state = workflow_state_service_ins.get_workflow_state_by_id(destination_state_id)
  1061. # if not destination_state:
  1062. # return False, msg
  1063. # 获取目标状态的信息
  1064. flag, participant_info = cls.get_ticket_state_participant_info(destination_state_id, ticket_id,
  1065. ticket_req_dict=request_data_dict)
  1066. if not flag:
  1067. return False, participant_info
  1068. destination_participant_type_id = participant_info.get('destination_participant_type_id', 0)
  1069. destination_participant = participant_info.get('destination_participant', '')
  1070. multi_all_person = participant_info.get('multi_all_person', '')
  1071. # 如果开启了了记忆最后处理人,且当前状态非全部处理中,那么处理人为之前的处理人
  1072. if destination_state.remember_last_man_enable and multi_all_person == '{}':
  1073. # 获取此状态的最后处理人
  1074. flag, result = cls.get_ticket_state_last_man(ticket_id, destination_state.id)
  1075. if flag and result.get('last_man'):
  1076. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1077. destination_participant = result.get('last_man')
  1078. # 更新工单信息:基础字段及自定义字段, add_relation字段 需要下个处理人是部门、角色等的情况
  1079. ticket_obj.state_id = destination_state_id
  1080. ticket_obj.participant_type_id = destination_participant_type_id
  1081. ticket_obj.participant = destination_participant
  1082. ticket_obj.multi_all_person = multi_all_person
  1083. if destination_state.type_id == constant_service_ins.STATE_TYPE_END:
  1084. ticket_obj.act_state_id = constant_service_ins.TICKET_ACT_STATE_FINISH
  1085. elif destination_state.type_id == constant_service_ins.STATE_TYPE_START:
  1086. ticket_obj.act_state_id = constant_service_ins.TICKET_ACT_STATE_DRAFT
  1087. else:
  1088. ticket_obj.act_state_id = constant_service_ins.TICKET_ACT_STATE_ONGOING
  1089. if req_transition_obj.attribute_type_id == constant_service_ins.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
  1090. ticket_obj.act_state_id = constant_service_ins.TICKET_ACT_STATE_BACK
  1091. ticket_obj.save()
  1092. # 记录处理过的人
  1093. if not (by_timer or by_task or by_hook):
  1094. cls.update_ticket_worked(ticket_id, username)
  1095. # 更新工单信息:基础字段及自定义字段, add_relation字段 需要考虑下个处理人是部门、角色等的情况
  1096. flag, result = cls.get_ticket_dest_relation(destination_participant_type_id, destination_participant)
  1097. if flag and result.get('add_relation'):
  1098. cls.update_ticket_relation(ticket_id, result.get('add_relation')) # 更新关系人信息
  1099. # 只更新需要更新的字段
  1100. update_field_dict = {}
  1101. for key, value in request_data_dict.items():
  1102. if key in update_field_list:
  1103. update_field_dict[key] = value
  1104. cls.update_ticket_field_value(ticket_id, update_field_dict)
  1105. # 更新工单流转记录,执行必要的脚本,通知消息
  1106. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  1107. if flag is False:
  1108. return False, result
  1109. ticket_all_data = result.get('all_field_value_json')
  1110. if not by_task:
  1111. # 脚本执行完自动触发的流转,因为在run_flow_task已经有记录操作日志,所以此次不再记录
  1112. cls.add_ticket_flow_log(dict(ticket_id=ticket_id, transition_id=transition_id, suggestion=suggestion,
  1113. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1114. participant=username, state_id=source_ticket_state_id, creator=username,
  1115. ticket_data=ticket_all_data))
  1116. # 通知消息
  1117. from tasks import send_ticket_notice
  1118. send_ticket_notice.apply_async(args=[ticket_id], queue='loonflow')
  1119. # 定时器逻辑
  1120. cls.handle_timer_transition(ticket_id, destination_state_id)
  1121. # 父工单逻辑处理
  1122. if destination_state.type_id == constant_service_ins.STATE_TYPE_END and ticket_obj.parent_ticket_id \
  1123. and ticket_obj.parent_ticket_state_id:
  1124. # 如果存在父工单,判断是否该父工单的下属子工单都已经结束状态,如果都是结束状态则自动流转父工单到下个状态
  1125. filter_params = dict(
  1126. parent_ticket_id=ticket_obj.parent_ticket_id,
  1127. parent_ticket_state_id=ticket_obj.parent_ticket_state_id,
  1128. is_deleted=0
  1129. )
  1130. other_sub_ticket_queryset = TicketRecord.objects.filter(**filter_params).all()
  1131. # 所有子工单使用相同的工作流,所以state都一样,检测是否都是ticket_obj.state_id即可判断是否都是结束状态
  1132. other_sub_ticket_state_id_list = [other_sub_ticket.state_id
  1133. for other_sub_ticket in other_sub_ticket_queryset]
  1134. flag, result = workflow_state_service_ins.get_states_info_by_state_id_list(
  1135. other_sub_ticket_state_id_list)
  1136. if flag:
  1137. sub_ticket_state_type_list = []
  1138. for key, value in result.items():
  1139. sub_ticket_state_type_list.append(value.get('type_id'))
  1140. list_set = set(sub_ticket_state_type_list)
  1141. if list_set == {constant_service_ins.STATE_TYPE_END}:
  1142. parent_ticket_obj = TicketRecord.objects.filter(id=ticket_obj.parent_ticket_id,
  1143. is_deleted=0).first()
  1144. parent_ticket_state_id = parent_ticket_obj.state_id
  1145. flag, parent_ticket_transition_queryset = workflow_transition_service_ins \
  1146. .get_state_transition_queryset(parent_ticket_state_id)
  1147. # 含有子工单的工单状态只支持单路径流转到下个状态
  1148. parent_ticket_transition_id = parent_ticket_transition_queryset[0].id
  1149. cls.handle_ticket(parent_ticket_obj.id, dict(transition_id=parent_ticket_transition_id,
  1150. username='loonrobot',
  1151. suggestion='所有子工单处理完毕,自动流转'))
  1152. if destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROBOT:
  1153. from tasks import run_flow_task # 放在文件开头会存在循环引用
  1154. run_flow_task.apply_async(args=[ticket_id, destination_participant, destination_state_id], queue='loonflow')
  1155. # 如果下个状态是hook,开始触发hook
  1156. if destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_HOOK:
  1157. from tasks import flow_hook_task # 放在文件开头会存在循环引用
  1158. flow_hook_task.apply_async(args=[ticket_id], queue='loonflow')
  1159. return True, ''
  1160. @classmethod
  1161. @auto_log
  1162. def add_ticket_relation(cls, ticket_id: int, user_str: str)->tuple:
  1163. """
  1164. 新增工单关系人
  1165. add ticket's relation
  1166. :param ticket_id:
  1167. :param user_str: 逗号隔开的
  1168. :return:
  1169. """
  1170. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=False).first()
  1171. new_relation_set = set(ticket_obj.relation.split(',') + user_str.split(',')) # 去重, 但是可能存在空元素
  1172. new_relation_list = [new_relation0 for new_relation0 in new_relation_set if new_relation0] # 去掉空元素
  1173. new_relation = ','.join(new_relation_list) # 去重
  1174. ticket_obj.relation = new_relation
  1175. ticket_obj.save()
  1176. return True, dict(new_relation=new_relation)
  1177. @classmethod
  1178. @auto_log
  1179. def update_ticket_relation(cls, ticket_id: int, user_str: str, ticket_creator: str='')->tuple:
  1180. """
  1181. 更新工单关系人
  1182. :param ticket_id:
  1183. :param user_str:
  1184. :param ticket_creator:
  1185. :return:
  1186. """
  1187. # ticket record table, for display ticket detail
  1188. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=False).first()
  1189. new_relation_set = set(ticket_obj.relation.split(',') + user_str.split(',') + [ticket_creator]) # 去重, 但是可能存在空元素
  1190. new_relation_list = [new_relation0 for new_relation0 in new_relation_set if new_relation0] # 去掉空元素
  1191. new_relation = ','.join(new_relation_list) # 去重
  1192. ticket_obj.relation = new_relation
  1193. ticket_obj.save()
  1194. # ticket user table, for ticket list query
  1195. if ticket_creator:
  1196. # if ticket_creator is not null, is mean new ticket, should add creator
  1197. new_record = TicketUser(ticket_id=ticket_id, username=ticket_creator)
  1198. new_record.save()
  1199. user_str_list = user_str.split(',')
  1200. existed_record_queryset = TicketUser.objects.filter(ticket_id=ticket_id, username__in=user_str_list).all()
  1201. user_need_update_list = [existed_record.username for existed_record in existed_record_queryset]
  1202. user_need_add_list = [user_str for user_str in user_str_list if user_str not in user_need_update_list]
  1203. TicketUser.objects.filter(ticket_id=ticket_id, username__in=user_need_update_list).update(in_process=True)
  1204. insert_list = []
  1205. for user_need_add in user_need_add_list:
  1206. insert_list.append(TicketUser(ticket_id=ticket_id, username=user_need_add, in_process=True))
  1207. TicketUser.objects.bulk_create(insert_list)
  1208. # 非在user_str中的 更新为in_process=False
  1209. TicketUser.objects.filter(ticket_id=ticket_id).exclude(username__in=user_str_list).all().update(in_process=False)
  1210. return True, ''
  1211. @classmethod
  1212. @auto_log
  1213. def update_ticket_worked(cls, ticket_id: int, username: str)-> tuple:
  1214. """
  1215. 更新工单处理过的人
  1216. :param ticket_id:
  1217. :param username:
  1218. :return:
  1219. """
  1220. worked_queryset = TicketUser.objects.filter(ticket_id=ticket_id, is_deleted=0, username=username).all()
  1221. if worked_queryset:
  1222. worked_queryset.update(worked=True, in_process=False)
  1223. else:
  1224. new_worked_record = TicketUser(ticket_id=ticket_id, username=username, in_process=False, worked=True)
  1225. new_worked_record.save()
  1226. return True, ''
  1227. @classmethod
  1228. @auto_log
  1229. def get_ticket_dest_relation(cls, destination_participant_type_id: int, destination_participant: str)->tuple:
  1230. """
  1231. 获取目标处理人相应的工单关系人
  1232. get ticket target participant's relation
  1233. :param destination_participant_type_id:
  1234. :param destination_participant:
  1235. :return:
  1236. """
  1237. if destination_participant_type_id in (constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1238. constant_service_ins.PARTICIPANT_TYPE_MULTI):
  1239. add_relation = destination_participant
  1240. elif destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_DEPT:
  1241. flag, username_list = account_base_service_ins.get_dept_username_list(destination_participant)
  1242. if flag is False:
  1243. return False, username_list
  1244. add_relation = ','.join(username_list)
  1245. elif destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROLE:
  1246. flag, username_list = account_base_service_ins.get_role_username_list(int(destination_participant))
  1247. if flag is False:
  1248. return False, username_list
  1249. add_relation = ','.join(username_list)
  1250. else:
  1251. add_relation = ''
  1252. return True, dict(add_relation=add_relation)
  1253. @classmethod
  1254. @auto_log
  1255. def get_ticket_flow_log(cls, ticket_id: int, username: str, per_page: int=10, page: int=1, ticket_data=0, desc=1)->tuple:
  1256. """
  1257. 获取工单流转记录
  1258. get ticket's flow log
  1259. :param ticket_id:
  1260. :param username:
  1261. :param per_page:
  1262. :param page:
  1263. :param ticket_data: 是否返回当前工单所有字段信息
  1264. :param desc: 是否降序
  1265. :return:
  1266. """
  1267. if desc == 0:
  1268. ticket_flow_log_queryset = TicketFlowLog.objects.filter(ticket_id=ticket_id, is_deleted=0).all().order_by('id')
  1269. else:
  1270. ticket_flow_log_queryset = TicketFlowLog.objects.filter(ticket_id=ticket_id, is_deleted=0).all().order_by(
  1271. '-id')
  1272. paginator = Paginator(ticket_flow_log_queryset, per_page)
  1273. try:
  1274. ticket_result_paginator = paginator.page(page)
  1275. except PageNotAnInteger:
  1276. ticket_result_paginator = paginator.page(1)
  1277. except EmptyPage:
  1278. # If page is out of range (e.g. 9999), deliver last page of results
  1279. ticket_result_paginator = paginator.page(paginator.num_pages)
  1280. ticket_flow_log_restful_list = []
  1281. for ticket_flow_log in ticket_result_paginator.object_list:
  1282. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(ticket_flow_log.state_id)
  1283. flag, result = cls.get_flow_log_transition_name(ticket_flow_log.transition_id,
  1284. ticket_flow_log.intervene_type_id)
  1285. if flag is False:
  1286. return False, result
  1287. transition_name = result.get('transition_name')
  1288. attribute_type_id = result.get('attribute_type_id')
  1289. state_info_dict = dict(state_id=state_obj.id, state_name=state_obj.name)
  1290. transition_info_dict = dict(transition_id=ticket_flow_log.transition_id, transition_name=transition_name,
  1291. attribute_type_id=attribute_type_id)
  1292. participant_info = dict(participant_type_id=ticket_flow_log.participant_type_id,
  1293. participant=ticket_flow_log.participant,
  1294. participant_alias=ticket_flow_log.participant,
  1295. participant_email='', participant_phone=''
  1296. )
  1297. if ticket_flow_log.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_PERSONAL:
  1298. flag, participant_query_obj = account_base_service_ins.get_user_by_username(ticket_flow_log.participant)
  1299. if flag:
  1300. participant_info.update(participant_alias=participant_query_obj.alias,
  1301. participant_email=participant_query_obj.email,
  1302. participant_phone=participant_query_obj.phone
  1303. )
  1304. ticket_flow_log_restful = dict(id=ticket_flow_log.id, ticket_id=ticket_id, state=state_info_dict,
  1305. transition=transition_info_dict,
  1306. intervene_type_id=ticket_flow_log.intervene_type_id,
  1307. participant_type_id=ticket_flow_log.participant_type_id,
  1308. participant=ticket_flow_log.participant,
  1309. participant_info=participant_info,
  1310. suggestion=ticket_flow_log.suggestion,
  1311. gmt_created=str(ticket_flow_log.gmt_created)[:19]
  1312. )
  1313. if ticket_data:
  1314. ticket_flow_log_restful.update(ticket_data=json.loads(ticket_flow_log.ticket_data))
  1315. ticket_flow_log_restful_list.append(dict(ticket_flow_log_restful))
  1316. return True, dict(ticket_flow_log_restful_list=ticket_flow_log_restful_list,
  1317. paginator_info=dict(per_page=per_page, page=page, total=paginator.count))
  1318. @classmethod
  1319. @auto_log
  1320. def get_ticket_flow_step(cls, ticket_id: int, username: str)->tuple:
  1321. """
  1322. 工单的流转步骤,路径。直线流转, 步骤不会很多(因为同个状态只显示一次,隐藏的状态只有当前处于才显示,否则不显示),默认先不分页
  1323. get ticket flow step info
  1324. :param ticket_id:
  1325. :param username:
  1326. :return:
  1327. """
  1328. # 先获取工单对应工作流的信息
  1329. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1330. if not ticket_obj:
  1331. return False, '工单不存在或已被删除'
  1332. workflow_id = ticket_obj.workflow_id
  1333. flag, state_objs = workflow_state_service_ins.get_workflow_states(workflow_id)
  1334. ticket_flow_log_queryset = TicketFlowLog.objects.filter(ticket_id=ticket_id, is_deleted=0).all()
  1335. state_step_dict_list = []
  1336. for state_obj in state_objs:
  1337. if state_obj.id == ticket_obj.state_id or (not state_obj.is_hidden):
  1338. ticket_state_step_dict = dict(state_id=state_obj.id, state_name=state_obj.name, order_id=state_obj.order_id)
  1339. state_flow_log_list = []
  1340. for ticket_flow_log in ticket_flow_log_queryset:
  1341. if ticket_flow_log.state_id == state_obj.id:
  1342. # 此部分和get_ticket_flow_log代码冗余,后续会简化下
  1343. flag, result = cls.get_flow_log_transition_name(ticket_flow_log.transition_id, ticket_flow_log.intervene_type_id)
  1344. if flag is False:
  1345. return False, result
  1346. transition_name = result.get('transition_name')
  1347. attribute_type_id = result.get('attribute_type_id')
  1348. participant_info = dict(participant_type_id=ticket_flow_log.participant_type_id,
  1349. participant=ticket_flow_log.participant,
  1350. participant_alias=ticket_flow_log.participant,
  1351. participant_email='', participant_phone=''
  1352. )
  1353. if ticket_flow_log.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_PERSONAL:
  1354. flag, participant_query_obj = account_base_service_ins.get_user_by_username(
  1355. ticket_flow_log.participant)
  1356. if flag:
  1357. participant_info.update(participant_alias=participant_query_obj.alias,
  1358. participant_email=participant_query_obj.email,
  1359. participant_phone=participant_query_obj.phone
  1360. )
  1361. state_flow_log_list.append(dict(id=ticket_flow_log.id, transition=dict(
  1362. transition_name=transition_name, transition_id=ticket_flow_log.transition_id),
  1363. participant_type_id=ticket_flow_log.participant_type_id,
  1364. participant=ticket_flow_log.participant,
  1365. participant_info=participant_info,
  1366. intervene_type_id=ticket_flow_log.intervene_type_id,
  1367. suggestion=ticket_flow_log.suggestion,
  1368. state_id=ticket_flow_log.state_id,
  1369. attribute_type_id=attribute_type_id,
  1370. gmt_created=str(ticket_flow_log.gmt_created)[:19]))
  1371. state_flow_log_list = sorted(state_flow_log_list, key=lambda keys: keys['id'],reverse=True)
  1372. ticket_state_step_dict['state_flow_log_list'] = state_flow_log_list
  1373. state_step_dict_list.append(ticket_state_step_dict)
  1374. state_step_dict_list = sorted(state_step_dict_list, key=lambda keys: keys['order_id'])
  1375. return True, dict(state_step_dict_list=state_step_dict_list, current_state_id=ticket_obj.state_id)
  1376. @classmethod
  1377. @auto_log
  1378. def get_flow_log_transition_name(cls, transition_id, intervene_type_id):
  1379. """
  1380. 获取流转日志中流转名称
  1381. :param transition_id:
  1382. :param intervene_type_id:
  1383. :return:
  1384. """
  1385. if transition_id:
  1386. flag, transition_obj = workflow_transition_service_ins.get_workflow_transition_by_id(transition_id)
  1387. if not transition_obj:
  1388. transition_name = '未知操作'
  1389. attribute_type_id = constant_service_ins.TRANSITION_ATTRIBUTE_TYPE_OTHER
  1390. else:
  1391. transition_name = transition_obj.name
  1392. attribute_type_id = transition_obj.attribute_type_id
  1393. else:
  1394. if intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_DELIVER:
  1395. transition_name = '转交操作'
  1396. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_ADD_NODE:
  1397. transition_name = '加签操作'
  1398. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_ADD_NODE_END:
  1399. transition_name = '加签完成操作'
  1400. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_ACCEPT:
  1401. transition_name = '接单操作'
  1402. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_COMMENT:
  1403. transition_name = '新增评论'
  1404. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_CLOSE:
  1405. transition_name = '强制关闭'
  1406. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_ALTER_STATE:
  1407. transition_name = '强制修改状态'
  1408. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_HOOK:
  1409. transition_name = 'hook操作'
  1410. elif intervene_type_id == constant_service_ins.TRANSITION_INTERVENE_TYPE_RETREAT:
  1411. transition_name = '撤回操作'
  1412. else:
  1413. transition_name = '未知操作'
  1414. attribute_type_id = constant_service_ins.TRANSITION_ATTRIBUTE_TYPE_OTHER
  1415. return True, dict(transition_name=transition_name, attribute_type_id=attribute_type_id)
  1416. @classmethod
  1417. @auto_log
  1418. def update_ticket_state(cls, ticket_id: int, state_id: int, username: str, suggestion: str)->tuple:
  1419. """
  1420. 更新状态id
  1421. update ticket's state by ticket_id, state_id, username
  1422. :param ticket_id:
  1423. :param state_id:
  1424. :param username:
  1425. :param suggestion:
  1426. :return:
  1427. """
  1428. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1429. if not ticket_obj:
  1430. return False, '工单不存在'
  1431. source_state_id = ticket_obj.state_id
  1432. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(state_id)
  1433. if not state_obj:
  1434. return False, state_obj
  1435. if state_obj.workflow_id == ticket_obj.workflow_id:
  1436. # 获取目标状态的处理人信息
  1437. flag, destination_participant_info = cls.get_ticket_state_participant_info(state_id, ticket_id=ticket_id)
  1438. ticket_obj.state_id = state_id
  1439. ticket_obj.participant_type_id = destination_participant_info.get('destination_participant_type_id', 0)
  1440. ticket_obj.participant = destination_participant_info.get('destination_participant', '')
  1441. ticket_obj.multi_all_person = '{}'
  1442. ticket_obj.save()
  1443. if destination_participant_info.get('destination_participant_type_id', 0) in (
  1444. constant_service_ins.PARTICIPANT_TYPE_PERSONAL, constant_service_ins.PARTICIPANT_TYPE_MULTI):
  1445. cls.update_ticket_relation(ticket_id, destination_participant_info.get('destination_participant', ''))
  1446. # 新增流转记录
  1447. # 获取工单所有字段的值
  1448. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  1449. if flag is False:
  1450. return False, result
  1451. all_ticket_data_json = result.get('all_field_value_json')
  1452. cls.add_ticket_flow_log(dict(ticket_id=ticket_id, transition_id=0, suggestion=suggestion,
  1453. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1454. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_ALTER_STATE,
  1455. participant=username, state_id=source_state_id,
  1456. ticket_data=all_ticket_data_json))
  1457. # 如果目标状态是脚本处理中,需要触发脚本处理
  1458. if ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROBOT:
  1459. from tasks import run_flow_task # 放在文件开头会存在循环引用
  1460. run_flow_task.apply_async(args=[ticket_id, ticket_obj.participant, ticket_obj.state_id],
  1461. queue='loonflow')
  1462. # 目标状态处理人类型是hook,需要触发hook
  1463. elif ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_HOOK:
  1464. from tasks import flow_hook_task # 放在文件开头会存在循环引用
  1465. flow_hook_task.apply_async(args=[ticket_id], queue='loonflow')
  1466. else:
  1467. # 通知消息
  1468. from tasks import send_ticket_notice
  1469. send_ticket_notice.apply_async(args=[ticket_id], queue='loonflow')
  1470. return True, '修改工单状态成功'
  1471. @classmethod
  1472. @auto_log
  1473. def get_tickets_states_by_ticket_id_list(cls, ticket_id_list: list, username: str)->tuple:
  1474. """
  1475. 批量获取工单状态
  1476. get ticket's state by ticket_id_list
  1477. :param ticket_id_list:
  1478. :param username:
  1479. :return:
  1480. """
  1481. ticket_queryset = TicketRecord.objects.filter(id__in=ticket_id_list).all()
  1482. ticket_state_id_dict = {}
  1483. for ticket in ticket_queryset:
  1484. ticket_state_id_dict[ticket.id] = ticket.state_id
  1485. ticket_state_id_list = [ticket_obj.state_id for ticket_obj in ticket_queryset]
  1486. flag, state_info_dict = workflow_state_service_ins.get_states_info_by_state_id_list(ticket_state_id_list)
  1487. new_result = {}
  1488. for key, value in ticket_state_id_dict.items():
  1489. new_result[key] = dict(state_id=value, state_name=state_info_dict[value]['name'])
  1490. return True, new_result
  1491. @classmethod
  1492. @auto_log
  1493. def accept_ticket(cls, ticket_id: int, username: str)->tuple:
  1494. """
  1495. accept ticket
  1496. :param ticket_id:
  1497. :param username:
  1498. :return:
  1499. """
  1500. # check handle permission
  1501. flag, result = cls.ticket_handle_permission_check(ticket_id, username)
  1502. if not flag:
  1503. return False, result
  1504. if not result.get('permission'):
  1505. return False, result.get('msg')
  1506. if result.get('need_accept'):
  1507. # update ticket relation people
  1508. cls.update_ticket_relation(ticket_id, username)
  1509. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1510. ticket_obj.participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1511. ticket_obj.participant = username
  1512. ticket_obj.save()
  1513. # add ticket flow log
  1514. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  1515. if flag is False:
  1516. return False, result
  1517. all_ticket_data_json = result.get('all_field_value_json')
  1518. ticket_flow_log_dict = dict(ticket_id=ticket_id, transition_id=0, suggestion='接单处理',
  1519. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1520. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_ACCEPT,
  1521. participant=username, state_id=ticket_obj.state_id, creator=username,
  1522. ticket_data=all_ticket_data_json)
  1523. cls.add_ticket_flow_log(ticket_flow_log_dict)
  1524. return True, ''
  1525. else:
  1526. return False, 'participant is one people, do not accept ticket first'
  1527. @classmethod
  1528. @auto_log
  1529. def deliver_ticket(cls, ticket_id: int, username: str, target_username: str, suggestion: str)->tuple:
  1530. """
  1531. deliver ticket to other
  1532. :param ticket_id:
  1533. :param username: operator username
  1534. :param target_username: deliver to username
  1535. :param suggestion: suggestion for deliver
  1536. :return:
  1537. """
  1538. cls.update_ticket_relation(ticket_id, target_username)
  1539. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1540. ticket_obj.participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1541. ticket_obj.participant = target_username
  1542. ticket_obj.save()
  1543. # add flow log
  1544. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  1545. if flag is False:
  1546. return False, result
  1547. all_ticket_data_json = result.get('all_field_value_json')
  1548. ticket_flow_log_dict = dict(ticket_id=ticket_id, transition_id=0, suggestion=suggestion,
  1549. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1550. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_DELIVER,
  1551. participant=username, state_id=ticket_obj.state_id, creator=username,
  1552. ticket_data=all_ticket_data_json)
  1553. cls.add_ticket_flow_log(ticket_flow_log_dict)
  1554. return True, ''
  1555. @classmethod
  1556. @auto_log
  1557. def add_node_ticket(cls, ticket_id: int, username: str, target_username: str, suggestion: str):
  1558. """
  1559. add node to other (加签工单)
  1560. :param ticket_id:
  1561. :param username:
  1562. :param target_username: add node to(加签目标人)
  1563. :param suggestion: add node suggestion(加签处理意见)
  1564. :return:
  1565. """
  1566. flag, result = cls.ticket_handle_permission_check(ticket_id, username)
  1567. if flag is False:
  1568. return False, result
  1569. if result.get('permission') is False:
  1570. return False, result.get('msg')
  1571. cls.update_ticket_relation(ticket_id, target_username)
  1572. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1573. ticket_obj.participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1574. ticket_obj.participant = target_username
  1575. ticket_obj.in_add_node = True
  1576. ticket_obj.add_node_man = username
  1577. ticket_obj.save()
  1578. # add flow log
  1579. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  1580. all_ticket_data_json = result.get('all_field_value_json')
  1581. ticket_flow_log_dict = dict(ticket_id=ticket_id, transition_id=0, suggestion=suggestion,
  1582. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1583. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_ADD_NODE,
  1584. participant=username, state_id=ticket_obj.state_id, creator=username,
  1585. ticket_data=all_ticket_data_json)
  1586. cls.add_ticket_flow_log(ticket_flow_log_dict)
  1587. return True, ''
  1588. @classmethod
  1589. @auto_log
  1590. def add_node_ticket_end(cls, ticket_id: int, username: str, suggestion: str):
  1591. """
  1592. add node finish(加签工单完成)
  1593. :param ticket_id:
  1594. :param username:
  1595. :param suggestion:
  1596. :return:
  1597. """
  1598. flag, result = cls.ticket_handle_permission_check(ticket_id, username)
  1599. if flag is False:
  1600. return False, result
  1601. if result.get('permission') is False:
  1602. return False, result.get('msg')
  1603. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1604. ticket_obj.participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1605. ticket_obj.participant = ticket_obj.add_node_man
  1606. ticket_obj.in_add_node = False
  1607. ticket_obj.add_node_man = ''
  1608. ticket_obj.save()
  1609. # 更新关系人表
  1610. cls.update_ticket_relation(ticket_id, ticket_obj.participant)
  1611. # add flow log
  1612. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  1613. if flag is False:
  1614. return False, result
  1615. all_ticket_data_json = result.get('all_field_value_json')
  1616. ticket_flow_log_dict = dict(ticket_id=ticket_id, transition_id=0, suggestion=suggestion,
  1617. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1618. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
  1619. participant=username, state_id=ticket_obj.state_id, creator=username,
  1620. ticket_data=all_ticket_data_json)
  1621. cls.add_ticket_flow_log(ticket_flow_log_dict)
  1622. return True, ''
  1623. @classmethod
  1624. @auto_log
  1625. def handle_timer_transition(cls, ticket_id: int, destination_state_id: int)->tuple:
  1626. """
  1627. 定时器处理
  1628. :param ticket_id:
  1629. :param destination_state_id:
  1630. :return:
  1631. """
  1632. # 定时器处理逻辑,如果新的状态所属transition有配置定时器,那么创建一个定时器流转的任务
  1633. flag, destination_transition_queryset = workflow_transition_service_ins.get_state_transition_queryset(
  1634. destination_state_id)
  1635. if destination_transition_queryset:
  1636. for destination_transition in destination_transition_queryset:
  1637. if destination_transition.timer:
  1638. from tasks import timer_transition
  1639. timer_transition.apply_async(args=[ticket_id, destination_state_id, datetime.datetime.now(),
  1640. destination_transition.id],
  1641. countdown=destination_transition.timer, queue='loonflow')
  1642. return True, ''
  1643. @classmethod
  1644. @auto_log
  1645. def get_ticket_all_field_value(cls, ticket_id: int)->tuple:
  1646. """
  1647. 工单所有字段的值
  1648. get ticket's all field value
  1649. :param ticket_id:
  1650. :return:
  1651. """
  1652. # 工单基础字段、工单自定义字段
  1653. ticket_obj = TicketRecord.objects.filter(id=ticket_id).first()
  1654. if not ticket_obj:
  1655. return False, '工单已被删除或者不存在'
  1656. # 获取工单基础表中的字段中的字段信息
  1657. field_info_dict = ticket_obj.get_dict()
  1658. # 获取自定义字段的值
  1659. flag, result = workflow_custom_field_service_ins.get_workflow_custom_field_name_list(ticket_obj.workflow_id)
  1660. if flag is False:
  1661. return False, result
  1662. ticket_custom_field_key_list = result.get('ticket_custom_field_key_list')
  1663. for field_key in ticket_custom_field_key_list:
  1664. flag, result = cls.get_ticket_field_value(ticket_id, field_key)
  1665. if flag is False:
  1666. return False, result
  1667. field_info_dict[field_key] = result.get('value')
  1668. return True, field_info_dict
  1669. @classmethod
  1670. @auto_log
  1671. def get_ticket_all_field_value_json(cls, ticket_id: int) -> tuple:
  1672. """
  1673. get ticket all field value to json
  1674. :param ticket_id:
  1675. :return:
  1676. """
  1677. flag, result = cls.get_ticket_all_field_value(ticket_id)
  1678. if flag is False:
  1679. return False, result
  1680. for key, value in result.items():
  1681. if type(value) not in [int, str, bool, float]:
  1682. result[key] = str(result[key])
  1683. return True, dict(all_field_value_json=json.dumps(result))
  1684. @classmethod
  1685. @auto_log
  1686. def retry_ticket_script(cls, ticket_id: int, username: str)->tuple:
  1687. """
  1688. 重新执行工单脚本,或重新触发hook
  1689. retry ticket script or hook
  1690. :param ticket_id:
  1691. :param username:
  1692. :return:
  1693. """
  1694. # 判断工单表记录中最后一次脚本是否执行失败了,即script_run_last_result的值
  1695. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  1696. if not ticket_obj:
  1697. return False, 'Ticket is not existed or has been deleted'
  1698. # if ticket_obj.participant_type_id is not CONSTANT_SERVICE.PARTICIPANT_TYPE_ROBOT:
  1699. # return False, "The ticket's participant_type is not robot, do not allow retry"
  1700. if ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROBOT:
  1701. # 先重置上次执行结果
  1702. ticket_obj.script_run_last_result = True
  1703. ticket_obj.save()
  1704. from tasks import run_flow_task # 放在文件开头会存在循环引用问题
  1705. run_flow_task.apply_async(args=[ticket_id, ticket_obj.participant, ticket_obj.state_id,
  1706. '{}_retry'.format(username)], queue='loonflow')
  1707. return True, ''
  1708. elif ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_HOOK:
  1709. ticket_obj.script_run_last_result = True
  1710. ticket_obj.save()
  1711. from tasks import flow_hook_task
  1712. flow_hook_task.apply_async(args=[ticket_id], queue='loonflow')
  1713. return True, ''
  1714. else:
  1715. return False, "The ticket's participant_type is not robot or hook, do not allow retry"
  1716. @classmethod
  1717. @auto_log
  1718. def get_ticket_state_last_man(cls, ticket_id: int, state_id: int)->tuple:
  1719. """
  1720. 获取工单状态最后一次的处理人
  1721. get the last handler to ticket's state
  1722. :param ticket_id:
  1723. :param state_id:
  1724. :return:
  1725. """
  1726. flow_log_queryset = TicketFlowLog.objects.filter(ticket_id=ticket_id, state_id=state_id,
  1727. is_deleted=0).order_by('-id')
  1728. if flow_log_queryset:
  1729. last_flow_log = flow_log_queryset[0]
  1730. if last_flow_log.participant_type_id == 1:
  1731. # 为个人时才生效
  1732. return True, dict(last_man=last_flow_log.participant)
  1733. else:
  1734. return True, dict(last_man='', msg='handle_man is not personal')
  1735. return True, dict(last_man='', msg='the state has not handle man before')
  1736. @classmethod
  1737. @auto_log
  1738. def get_ticket_count_by_args(cls, workflow_id: int=0, username: str='', period: int=0)->tuple:
  1739. """
  1740. 获取工单的个数
  1741. get ticket's count by hour period
  1742. :param workflow_id:
  1743. :param username:
  1744. :param period: 周期, 单位小时,即多少小时内
  1745. :return:
  1746. """
  1747. query_params = Q(is_deleted=False)
  1748. if workflow_id:
  1749. query_params &= Q(workflow_id=workflow_id)
  1750. if username:
  1751. query_params &= Q(creator=username)
  1752. if period:
  1753. query_params &= Q(creator=username)
  1754. datetime_now = datetime.datetime.now()
  1755. datetime_start = datetime_now - datetime.timedelta(hours=period)
  1756. query_params &= Q(gmt_created__gte=datetime_start)
  1757. count_result = TicketRecord.objects.filter(query_params).count()
  1758. return True, dict(count_result=count_result)
  1759. @classmethod
  1760. @auto_log
  1761. def get_ticket_state_participant_info(cls, state_id: int, ticket_id: int=0, ticket_req_dict: dict={})->tuple:
  1762. """
  1763. 获取工单状态实际的新处理人
  1764. get ticket's new participant by state_id
  1765. :param state_id:
  1766. :param ticket_id: new ticket if no ticket id
  1767. :param ticket_req_dict:
  1768. :return:
  1769. """
  1770. if ticket_id:
  1771. flag, ticket_obj = cls.get_ticket_by_id(ticket_id)
  1772. if not flag:
  1773. return False, ticket_obj
  1774. # 初始状态判断
  1775. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(state_id)
  1776. if state_obj.type_id == constant_service_ins.STATE_TYPE_START:
  1777. # 回到初始状态,目标处理人应该为工单的发起人
  1778. return True, dict(destination_participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1779. destination_participant=ticket_obj.creator,
  1780. multi_all_person="{}")
  1781. elif state_obj.type_id == constant_service_ins.STATE_TYPE_END:
  1782. # 回到结束状态,目标处理人应该为空
  1783. return True, dict(destination_participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1784. destination_participant='',
  1785. multi_all_person="{}")
  1786. parent_ticket_id = ticket_obj.parent_ticket_id
  1787. creator = ticket_obj.creator
  1788. multi_all_person = json.loads(ticket_obj.multi_all_person)
  1789. else:
  1790. # 新建工单
  1791. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(state_id)
  1792. if state_obj.type_id == constant_service_ins.STATE_TYPE_START:
  1793. # 回到初始状态,目标处理人应该为工单的发起人
  1794. return True, dict(destination_participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1795. destination_participant=ticket_req_dict.get('username'),
  1796. multi_all_person="{}")
  1797. elif state_obj.type_id == constant_service_ins.STATE_TYPE_END:
  1798. # 回到结束状态,目标处理人应该为空
  1799. return True, dict(destination_participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  1800. destination_participant='',
  1801. multi_all_person="{}")
  1802. parent_ticket_id = ticket_req_dict.get('parent_ticket_id')
  1803. creator = ticket_req_dict.get('username')
  1804. multi_all_person = "{}"
  1805. participant_type_id, participant = state_obj.participant_type_id, state_obj.participant
  1806. destination_participant_type_id, destination_participant = participant_type_id, participant
  1807. if participant_type_id == constant_service_ins.PARTICIPANT_TYPE_FIELD:
  1808. if not ticket_id:
  1809. # ticket_id 不存在,则为新建工单,从请求的数据中获取
  1810. participant_list = participant.split(',')
  1811. destination_participant_list = []
  1812. for participant0 in participant_list:
  1813. destination_participant_list.append(ticket_req_dict.get(participant0, ''))
  1814. destination_participant = ','.join(destination_participant_list)
  1815. else:
  1816. # 工单存在,先判断是否有修改此字段的权限,如果有且字段值有提供,则取提交的值
  1817. flag, field_info = cls.get_state_field_info(ticket_obj.state_id)
  1818. update_field_list = field_info.get('update_field_list')
  1819. flag, ticket_value_info = cls.get_ticket_all_field_value(ticket_id)
  1820. participant_list = participant.split(',')
  1821. destination_participant_list = []
  1822. for participant0 in participant_list:
  1823. if participant0 in update_field_list and ticket_req_dict.get(participant0):
  1824. # 请求数据中包含需要的字段则从请求数据中获取
  1825. destination_participant_list.append(ticket_req_dict.get(participant0))
  1826. else:
  1827. # 处理工单时未提供字段的值,则从工单当前字段值中获取
  1828. destination_participant_list.append(ticket_value_info.get(participant0))
  1829. destination_participant = ','.join(destination_participant_list)
  1830. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1831. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_PARENT_FIELD:
  1832. flag, ticket_value_info = cls.get_ticket_all_field_value(parent_ticket_id)
  1833. participant_list = participant.split(',')
  1834. destination_participant_list = []
  1835. for participant0 in participant_list:
  1836. destination_participant_list.append(ticket_value_info.get(participant0))
  1837. destination_participant = ','.join(destination_participant_list)
  1838. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1839. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_VARIABLE:
  1840. participant_list = participant.split(',')
  1841. destination_participant_list = []
  1842. for participant0 in participant_list:
  1843. if participant0 == 'creator':
  1844. destination_participant_list.append(creator)
  1845. elif participant0 == 'creator_tl':
  1846. flag, approver = account_base_service_ins.get_user_dept_approver(creator)
  1847. if flag is False:
  1848. return False, approver
  1849. destination_participant_list.append(approver)
  1850. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1851. destination_participant = ','.join(destination_participant_list)
  1852. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_DEPT:
  1853. # 支持多部门
  1854. flag, destination_participant_list = account_base_service_ins.get_dept_username_list(
  1855. destination_participant)
  1856. destination_participant = ','.join(destination_participant_list)
  1857. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1858. if flag is False:
  1859. return False, destination_participant_list
  1860. elif destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROLE:
  1861. flag, destination_participant_list = account_base_service_ins.get_role_username_list(
  1862. int(destination_participant))
  1863. destination_participant = ','.join(destination_participant_list)
  1864. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1865. if flag is False:
  1866. return False, destination_participant_list
  1867. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_HOOK:
  1868. destination_participant = '***' # 敏感数据,不保存工单基础表中
  1869. elif participant_type_id == constant_service_ins.PARTICIPANT_TYPE_FROM_EXTERNAL:
  1870. import requests
  1871. external_config = json.loads(participant)
  1872. external_url = external_config.get('external_url')
  1873. external_token = external_config.get('external_token')
  1874. extra_info = external_config.get('extra_info')
  1875. flag, msg = common_service_ins.gen_hook_signature(external_token)
  1876. if not flag:
  1877. return False, msg
  1878. if not ticket_id:
  1879. all_ticket_data = ticket_req_dict
  1880. else:
  1881. flag, all_ticket_data = ticket_base_service_ins.get_ticket_all_field_value(ticket_id)
  1882. if extra_info is not None:
  1883. all_ticket_data.update(dict(extra_info=extra_info))
  1884. try:
  1885. r = requests.post(external_url, headers=msg, json=all_ticket_data, timeout=10)
  1886. result = r.json() # {code:0, msg:'', data:'zhangsan,lisi'}
  1887. if len(result.get('data').split(',')) > 1:
  1888. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_MULTI
  1889. else:
  1890. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  1891. destination_participant = result.get('data')
  1892. except Exception as e:
  1893. import logging
  1894. import traceback
  1895. logger = logging.getLogger('django')
  1896. logger.error('get external participant error:')
  1897. logger.error(traceback.format_exc())
  1898. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONA
  1899. destination_participant = 'admin'
  1900. # 参与人去重复+类型修正
  1901. if destination_participant_type_id in (constant_service_ins.PARTICIPANT_TYPE_PERSONAL, constant_service_ins.PARTICIPANT_TYPE_MULTI):
  1902. destination_participant_list = destination_participant.split(',')
  1903. destination_participant_list = list(set(destination_participant_list))
  1904. if len(destination_participant_list) > 1:
  1905. destination_participant_type_id = constant_service_ins.PARTICIPANT_TYPE_MULTI
  1906. destination_participant = ','.join(destination_participant_list)
  1907. if destination_participant_type_id == constant_service_ins.PARTICIPANT_TYPE_MULTI:
  1908. if state_obj.distribute_type_id == constant_service_ins.STATE_DISTRIBUTE_TYPE_RANDOM:
  1909. destination_participant = random.sample(destination_participant, 1)[0]
  1910. elif state_obj.distribute_type_id == constant_service_ins.STATE_DISTRIBUTE_TYPE_ALL:
  1911. multi_all_person_dict = {}
  1912. for destination_participant_0 in destination_participant.split(','):
  1913. multi_all_person_dict[destination_participant_0] = {}
  1914. multi_all_person = json.dumps(multi_all_person_dict)
  1915. return True, dict(destination_participant_type_id=destination_participant_type_id,
  1916. destination_participant=destination_participant,
  1917. multi_all_person=multi_all_person)
  1918. @classmethod
  1919. @auto_log
  1920. def get_state_field_info(cls, state_id: int)->tuple:
  1921. """
  1922. 获取状态字段信息
  1923. get state's field config
  1924. :param state_id:
  1925. :return:
  1926. """
  1927. flag, state_obj = workflow_state_service_ins.get_workflow_state_by_id(state_id)
  1928. if flag is False:
  1929. return False, state_obj
  1930. state_field_dict = json.loads(state_obj.state_field_str)
  1931. require_field_list, update_field_list = [], []
  1932. for key, value in state_field_dict.items():
  1933. if value == constant_service_ins.FIELD_ATTRIBUTE_REQUIRED:
  1934. require_field_list.append(key)
  1935. update_field_list.append(key)
  1936. if value == constant_service_ins.FIELD_ATTRIBUTE_OPTIONAL:
  1937. update_field_list.append(key)
  1938. return True, dict(require_field_list=require_field_list, update_field_list=update_field_list)
  1939. @classmethod
  1940. @auto_log
  1941. def get_next_state_id_by_transition_and_ticket_info(cls, ticket_id: int=0, ticket_req_dict: dict={})->tuple:
  1942. """
  1943. 获取工单的下个状态id,需要考虑条件流转的情况
  1944. get ticket's next state_id by transition
  1945. :param ticket_id:
  1946. :param ticket_req_dict:
  1947. :return:
  1948. """
  1949. transition_id = ticket_req_dict.get('transition_id', 0)
  1950. workflow_id = ticket_req_dict.get('workflow_id', 0)
  1951. if not transition_id:
  1952. return False, 'transition_id can not be None'
  1953. if not ticket_id:
  1954. # 新建工单的情况, 获取初始状态 作为原状态
  1955. # 获取transition_id对应的下个状态的信息:
  1956. # 新建工单获取工单的初始状态
  1957. if not workflow_id:
  1958. return False, 'new ticket need arg workflow_id'
  1959. flag, start_state = workflow_state_service_ins.get_workflow_start_state(workflow_id)
  1960. if flag is False:
  1961. return False, start_state
  1962. source_state_id = start_state.id
  1963. else:
  1964. # 已经存在的工单,直接获取工单当前状态
  1965. flag, ticket_obj = cls.get_ticket_by_id(ticket_id)
  1966. source_state_id = ticket_obj.state_id
  1967. flag, transition_queryset = workflow_transition_service_ins.get_transition_by_args(
  1968. dict(source_state_id=source_state_id, id=transition_id))
  1969. if not transition_queryset:
  1970. return False, 'transition_id is invalid'
  1971. transition_obj = transition_queryset[0]
  1972. condition_expression = transition_obj.condition_expression
  1973. destination_state_id = transition_obj.destination_state_id
  1974. if condition_expression and json.loads(condition_expression):
  1975. # 存在条件表达式,需要根据表达式计算下个状态
  1976. condition_expression_list = json.loads(condition_expression)
  1977. ticket_all_value_dict = {}
  1978. if ticket_id:
  1979. # 获取工单所有字段的值
  1980. flag, ticket_all_value_dict = cls.get_ticket_all_field_value(ticket_id)
  1981. # 更新当前更新的字段的值
  1982. ticket_all_value_dict_copy = copy.deepcopy(ticket_all_value_dict)
  1983. ticket_all_value_dict_copy.update(ticket_req_dict)
  1984. for key, value in ticket_all_value_dict_copy.items():
  1985. if isinstance(ticket_all_value_dict_copy[key], str):
  1986. ticket_all_value_dict_copy[key] = "'''" + ticket_all_value_dict_copy[key] + "'''"
  1987. for condition_expression0 in condition_expression_list:
  1988. expression = condition_expression0.get('expression')
  1989. expression_format = expression.format(**ticket_all_value_dict_copy)
  1990. import datetime, time # 用于支持条件表达式中对时间的操作
  1991. # 为了安全考虑,仅支持datetime, time, abs. 如果你需要其他库函数,可参考datetime、abs这些自行添加
  1992. if eval(expression_format, {'__builtins__': None}, {'datetime': datetime, 'time': time, 'abs': abs}):
  1993. destination_state_id = condition_expression0.get('target_state_id')
  1994. break
  1995. return True, dict(destination_state_id=destination_state_id)
  1996. @classmethod
  1997. @auto_log
  1998. def add_comment(cls, ticket_id: int=0, username: str='', suggestion: str='')->tuple:
  1999. """
  2000. 添加评论
  2001. add comment to ticket
  2002. :param ticket_id:
  2003. :param username:
  2004. :param suggestion:
  2005. :return:
  2006. """
  2007. if not (ticket_id and username):
  2008. return False, 'ticket_id and username should not be null'
  2009. flag, all_ticket_data = cls.get_ticket_all_field_value(ticket_id)
  2010. # date等格式需要转换为str
  2011. for key, value in all_ticket_data.items():
  2012. if type(value) not in [int, str, bool, float]:
  2013. all_ticket_data[key] = str(all_ticket_data[key])
  2014. all_ticket_data_json = json.dumps(all_ticket_data)
  2015. new_flow_log = dict(ticket_id=ticket_id, transition_id=0, suggestion=suggestion,
  2016. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  2017. participant=username, state_id=all_ticket_data.get('state_id'),
  2018. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_COMMENT,
  2019. ticket_data=all_ticket_data_json, creator=username)
  2020. flag, msg = cls.add_ticket_flow_log(new_flow_log)
  2021. if flag is False:
  2022. return False, msg
  2023. return True, ''
  2024. @classmethod
  2025. @auto_log
  2026. def hook_call_back(cls, ticket_id: int, app_name: str, request_data_dict: dict)->tuple:
  2027. """
  2028. hook回调
  2029. :param ticket_id:
  2030. :param app_name:
  2031. :param request_data_dict:
  2032. :return:
  2033. """
  2034. # 校验请求app_name是否有hook回调该工单权限
  2035. flag, msg = account_base_service_ins.app_ticket_permission_check(app_name, ticket_id)
  2036. if not flag:
  2037. return False, msg
  2038. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  2039. # 检查工单处理人类型为hook中
  2040. if ticket_obj.participant_type_id != constant_service_ins.PARTICIPANT_TYPE_HOOK:
  2041. return False, '工单当前处理人类型非hook,不执行回调操作'
  2042. result = request_data_dict.get('result', True)
  2043. msg = request_data_dict.get('msg', '')
  2044. field_value = request_data_dict.get('field_value', {}) # 用于更新字段
  2045. if not result:
  2046. # hook执行失败了,记录失败状态.以便允许下次再执行
  2047. cls.update_ticket_field_value(ticket_id,{'script_run_last_result': False})
  2048. # 记录错误信息
  2049. flag, result_data = ticket_base_service_ins.get_ticket_all_field_value_json(ticket_id)
  2050. all_ticket_data_json = result_data.get('all_field_value_json')
  2051. ticket_base_service_ins.add_ticket_flow_log(
  2052. dict(ticket_id=ticket_id, transition_id=0, suggestion=msg,
  2053. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_HOOK,
  2054. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_HOOK,
  2055. participant='hook', state_id=ticket_obj.state_id, ticket_data=all_ticket_data_json,
  2056. creator='hook'))
  2057. return True, ''
  2058. state_id = ticket_obj.state_id
  2059. flag, transition_queryset = workflow_transition_service_ins.get_state_transition_queryset(state_id)
  2060. transition_id = transition_queryset[0].id # hook状态只支持一个流转
  2061. new_request_dict = field_value
  2062. new_request_dict.update({'transition_id': transition_id, 'suggestion': msg, 'username': 'loonrobot'})
  2063. # 执行流转
  2064. flag, msg = cls.handle_ticket(ticket_id, new_request_dict, by_timer=False, by_task=False, by_hook=True)
  2065. if not flag:
  2066. return False, msg
  2067. return True, ''
  2068. @classmethod
  2069. @auto_log
  2070. def get_ticket_participant_info(cls, ticket_id: int)->tuple:
  2071. """
  2072. 获取工单当前详细参与人信息
  2073. get ticket's now participant info
  2074. :param ticket_id:
  2075. :return:
  2076. """
  2077. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  2078. from apps.account.models import LoonUser
  2079. participant_username_list, participant_info_list = [], []
  2080. if ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_PERSONAL:
  2081. participant_username_list = [ticket_obj.participant]
  2082. elif ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_MULTI:
  2083. participant_username_list = ticket_obj.participant.split(',')
  2084. elif ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_ROLE:
  2085. flag, participant_username_list = account_base_service_ins.get_role_username_list(ticket_obj.participant)
  2086. if flag is False:
  2087. return False, participant_username_list
  2088. elif ticket_obj.participant_type_id == constant_service_ins.PARTICIPANT_TYPE_DEPT:
  2089. flag, participant_username_list = account_base_service_ins.get_dept_username_list(ticket_obj.participant)
  2090. if flag is False:
  2091. return False, participant_username_list
  2092. if participant_username_list:
  2093. participant_queryset = LoonUser.objects.filter(username__in=participant_username_list, is_deleted=0)
  2094. for participant_0 in participant_queryset:
  2095. participant_info_list.append(dict(username=participant_0.username, alias=participant_0.alias,
  2096. phone=participant_0.phone, email=participant_0.email))
  2097. return True, dict(participant_username_list=participant_username_list,
  2098. participant_info_list=participant_info_list)
  2099. @classmethod
  2100. @auto_log
  2101. def close_ticket(cls, ticket_id: int, username: str, suggestion: str)->tuple:
  2102. """
  2103. 关闭工单
  2104. close ticket: set state to end state
  2105. :param ticket_id:
  2106. :param username:
  2107. :param suggestion:处理意见
  2108. :return:
  2109. """
  2110. # 获取工单详细信息
  2111. ticket_obj = TicketRecord.objects.filter(id=ticket_id, is_deleted=0).first()
  2112. if not ticket_obj:
  2113. return False, '工单不存在或已被删除'
  2114. workflow_id = ticket_obj.workflow_id
  2115. # 查询工作流的结束状态
  2116. flag, state_obj = workflow_state_service_ins.get_workflow_end_state(workflow_id)
  2117. if flag is False:
  2118. return False, state_obj
  2119. # 新增流转记录
  2120. # 获取工单所有字段的值
  2121. flag, result = cls.get_ticket_all_field_value_json(ticket_id)
  2122. if flag is False:
  2123. return False, result
  2124. all_ticket_data_json = result.get('all_field_value_json')
  2125. ticket_flow_log_dict = dict(ticket_id=ticket_id, transition_id=0, suggestion='强制关闭工单:{}'.format(suggestion),
  2126. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  2127. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_CLOSE,
  2128. participant=username, state_id=state_obj.id, ticket_data=all_ticket_data_json,
  2129. )
  2130. new_state_id = state_obj.id
  2131. ticket_obj.state_id = new_state_id
  2132. ticket_obj.participant_type_id = 0
  2133. ticket_obj.participant = ''
  2134. ticket_obj.act_state_id = constant_service_ins.TICKET_ACT_STATE_CLOSED
  2135. ticket_obj.save()
  2136. # 更新ticketuser中in_process状态
  2137. TicketUser.objects.filter(ticket_id=ticket_id, is_deleted=0).update(in_process=False)
  2138. cls.add_ticket_flow_log(ticket_flow_log_dict)
  2139. return True, ''
  2140. @classmethod
  2141. @auto_log
  2142. def delete_ticket(cls, ticket_id: int, username: str, suggestion: str)->tuple:
  2143. """
  2144. 删除工单,建议仅用于管理干预删除工单
  2145. :param ticket_id:
  2146. :param username:
  2147. :param suggestion:
  2148. :return:
  2149. """
  2150. flag, result = cls.get_ticket_by_id(ticket_id)
  2151. if flag is False:
  2152. return False, result
  2153. flag, result_data = ticket_base_service_ins.get_ticket_all_field_value_json(ticket_id)
  2154. all_ticket_data_json = result_data.get('all_field_value_json')
  2155. ticket_base_service_ins.add_ticket_flow_log(
  2156. dict(ticket_id=ticket_id, transition_id=0, suggestion=suggestion,
  2157. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_DELETE,
  2158. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  2159. participant=username, state_id=result.state_id, ticket_data=all_ticket_data_json, creator=username))
  2160. result.is_deleted = True
  2161. result.save()
  2162. return True, ''
  2163. @classmethod
  2164. @auto_log
  2165. def ticket_admin_permission_check(cls, ticket_id: int = 0, username: str = '') -> tuple:
  2166. """
  2167. 校验用户是否是该工单的工作流管理员,以判断是否有权限对该工单干预处理
  2168. :param username:
  2169. :param ticket_id:
  2170. :return:
  2171. """
  2172. # 超级管理员拥有所有工作流管理权限
  2173. flag, result = account_base_service_ins.get_user_by_username(username)
  2174. if flag is False:
  2175. return False, result
  2176. if result.type_id == constant_service_ins.ACCOUNT_TYPE_WORKFLOW_ADMIN:
  2177. return True, "admin user has all ticket's intervention manage permission"
  2178. flag, result = cls.get_ticket_by_id(ticket_id)
  2179. if flag is False:
  2180. return False, result
  2181. workflow_id = result.workflow_id
  2182. flag, result = workflow_base_service_ins.get_workflow_manage_list(username)
  2183. if flag is False:
  2184. return False, result
  2185. workflow_list = result.get('workflow_list')
  2186. workflow_id_list = [workflow['id'] for workflow in workflow_list]
  2187. if workflow_id in workflow_id_list:
  2188. return True, ''
  2189. else:
  2190. return False, 'user has no permission to manage this ticket'
  2191. @classmethod
  2192. @auto_log
  2193. def get_ticket_num_statistics(cls, start_date: str='', end_date: str='', username: str='') ->tuple:
  2194. """
  2195. 工单统计
  2196. :param start_date:
  2197. :param end_date:
  2198. :param username:
  2199. :return:
  2200. """
  2201. # 获取用户有权限的工作流
  2202. flag, result = workflow_base_service_ins.get_workflow_manage_list(username)
  2203. if flag is False:
  2204. return False, result
  2205. workflow_list = result.get('workflow_list')
  2206. workflow_id_list = [workflow.get('id') for workflow in workflow_list]
  2207. from django.db.models import Count
  2208. query_params = {'is_deleted': 0, 'workflow_id__in': workflow_id_list}
  2209. if start_date:
  2210. query_params['gmt_gte'] = start_date
  2211. if end_date:
  2212. query_params['gmt_gte'] = end_date
  2213. queryset_result = TicketRecord.objects.filter(**query_params).extra(
  2214. select={'year': 'year(gmt_created)', 'month': 'month(gmt_created)', 'day': 'day(gmt_created)',
  2215. 'workflow_id': 'workflow_id'}).values('year', 'month', 'day', 'workflow_id').annotate(
  2216. count_len=Count('gmt_created')).order_by()
  2217. workflow_id_dict = {}
  2218. for workflow in workflow_list:
  2219. workflow_id_dict[workflow.get('id')] = workflow
  2220. result_list = []
  2221. for queryset in queryset_result:
  2222. date_str = '%d-%02d-%02d' % (queryset['year'], queryset['month'], queryset['day'])
  2223. workflow_name = workflow_id_dict[queryset['workflow_id']]['name']
  2224. result_list.append(dict(day=date_str, type=workflow_name, count=queryset['count_len']))
  2225. # 按日期排序
  2226. result_list = sorted(result_list, key=lambda r: r['day'])
  2227. return True, dict(result_list=result_list)
  2228. @classmethod
  2229. @auto_log
  2230. def retreat_ticket(cls, ticket_id: int, username: str='', suggestion: str='')->tuple:
  2231. """
  2232. 撤回工单
  2233. :param ticket_id:
  2234. :param username:
  2235. :param suggestion:
  2236. :return:
  2237. """
  2238. # 判断用户是否有撤回权限,工单的创建人,且当前状态允许撤回
  2239. flag, ticket_result = cls.get_ticket_by_id(ticket_id)
  2240. if flag is False:
  2241. return False, ticket_result
  2242. if ticket_result.creator != username:
  2243. return False, "just ticket's creator can retreat ticket in specific state that enable retreat"
  2244. workflow_id = ticket_result.workflow_id
  2245. flag, result = workflow_state_service_ins.get_workflow_state_by_id(ticket_result.state_id)
  2246. if flag is False:
  2247. return False, result
  2248. if result.enable_retreat is False:
  2249. return False, 'now state can not be retreat'
  2250. flag, result = workflow_state_service_ins.get_workflow_start_state(workflow_id)
  2251. if flag is False:
  2252. return False, result
  2253. ticket_result.state_id = result.id
  2254. ticket_result.participant_type_id = constant_service_ins.PARTICIPANT_TYPE_PERSONAL
  2255. ticket_result.participant = ticket_result.creator
  2256. ticket_result.act_state_id = constant_service_ins.TICKET_ACT_STATE_RETREAT
  2257. ticket_result.save()
  2258. cls.update_ticket_relation(ticket_id, ticket_result.creator)
  2259. # 新增操作记录
  2260. flag, result = cls.get_ticket_all_field_value_json(ticket_result.id)
  2261. if flag is False:
  2262. return False, result
  2263. all_ticket_data_json = result.get('all_field_value_json')
  2264. new_ticket_flow_log_dict = dict(ticket_id=ticket_result.id, transition_id=0, suggestion=suggestion,
  2265. intervene_type_id=constant_service_ins.TRANSITION_INTERVENE_TYPE_RETREAT,
  2266. participant_type_id=constant_service_ins.PARTICIPANT_TYPE_PERSONAL,
  2267. participant=username, state_id=ticket_result.state_id,
  2268. ticket_data=all_ticket_data_json
  2269. )
  2270. cls.add_ticket_flow_log(new_ticket_flow_log_dict)
  2271. return True, ''
  2272. def close_ticket_permission_check(cls, ticket_id: int, username: str)->tuple:
  2273. """
  2274. 用户是否有强制关闭工单的权限
  2275. :param ticket_id:
  2276. :param username:
  2277. :return:
  2278. """
  2279. # 强制关闭工单需要对应工作流的管理员或者超级管理员 或者处于初始状态的工单由创建人直接关闭
  2280. flag, result = ticket_base_service_ins.ticket_admin_permission_check(ticket_id, username)
  2281. if flag is False:
  2282. # 判断是否属于初始状态 且用户为工单创建人
  2283. flag, ticket_result = cls.get_ticket_by_id(ticket_id)
  2284. if flag is False:
  2285. return False, ticket_result
  2286. workflow_id = ticket_result.workflow_id
  2287. flag, start_state_result = workflow_state_service_ins.get_workflow_start_state(workflow_id)
  2288. if flag is False:
  2289. return False, start_state_result
  2290. if ticket_result.creator == username and ticket_result.state_id == start_state_result.id:
  2291. return True, "ticket's creator can close ticket in start state"
  2292. else:
  2293. return False, "just ticket's creator can close ticket in start state or workflow_admin can close ticket in any state"
  2294. else:
  2295. return True, "ticket's workflow admin casn close ticket in any state"
  2296. @classmethod
  2297. def upload_file(cls, request: any)->tuple:
  2298. import os, uuid
  2299. file_obj = request.FILES.get('file')
  2300. source_file_name = file_obj.name
  2301. source_file_type = source_file_name.split('.')[-1]
  2302. file_name = str(uuid.uuid1()) + '.' + source_file_type
  2303. f = open(os.path.join(settings.MEDIA_ROOT, 'ticket_file/{}'.format(file_name)), 'wb')
  2304. for chunk in file_obj.chunks():
  2305. f.write(chunk)
  2306. f.close()
  2307. return True, dict(file_name=file_name, file_path='/media/ticket_file/{}'.format(file_name), source_file_name=source_file_name)
  2308. ticket_base_service_ins = TicketBaseService()