Files
d3i-szct/models/govc_task_department_feedback.py
T
2026-06-02 17:46:38 +08:00

546 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import datetime
import random
from typing import Union
import pandas as pd
from sqlalchemy import select, delete
from tornado_swagger.model import register_swagger_model
from wtforms import StringField, TextAreaField, IntegerField, DateTimeField
from wtforms.validators import Length, Optional
import models
from models.common_model import CommonModel
from models.db_models import TD3iGovcTaskDepartmentFeedback
from paste.core.logging import echo_log
from paste.rbac.rbac_user import RbacUser
from paste.util.pagination import Pagination
from paste.web.form import ModelForm
class GovcTaskDeptFeedbackForm(ModelForm):
"""
部门处置反馈表单验证类(完全映射 TD3iGovcTaskDepartmentFeedback 字段)。
用于验证和处理市12345部门处置信息的创建/修改表单数据。
字段完全映射数据库表 t_d3i_govc_task_department_feedback 的字段结构。
"""
# 基础信息
id = IntegerField('主键ID')
task_id = IntegerField('关联工单主表ID', validators=[Optional()]) # 实际应根据业务调整必填性
zxhf_info = TextAreaField('专项回复信息')
back_info = TextAreaField('退回信息')
sign_time_bf = DateTimeField('签收时限', validators=[Optional()])
operation_text = StringField('操作描述', validators=[Length(max=255, message='操作描述长度不能超过255字符')])
opinion = TextAreaField('反馈意见')
unit = StringField('承办单位', validators=[Length(max=255, message='承办单位长度不能超过255字符')])
finish_time_bf = DateTimeField('反馈时限', validators=[Optional()])
person = StringField('承办人', validators=[Length(max=128, message='承办人长度不能超过128字符')])
sign_time = DateTimeField('签收时间', validators=[Optional()])
name = StringField('负责人', validators=[Length(max=128, message='负责人长度不能超过128字符')])
tel = StringField('联系电话', validators=[Length(max=64, message='联系电话长度不能超过64字符')])
time = DateTimeField('反馈时间', validators=[Optional()])
department = StringField('部门', validators=[Length(max=255, message='部门长度不能超过255字符')])
status = IntegerField('状态')
back_time_bf = DateTimeField('拒绝时限', validators=[Optional()])
def process(self, formdata=None, obj=None, **kwargs):
"""
处理表单数据,在数据绑定前进行预处理。
主要功能:
- 遍历所有表单字段
- 对字符串类型的值去除两端空白字符
- 调用父类的process方法继续处理
"""
if formdata:
for name, values in formdata.items():
if isinstance(values, list) and values:
formdata[name] = [v.strip() if isinstance(v, str) else v for v in values]
elif isinstance(values, str):
formdata[name] = values.strip()
super().process(formdata, obj, **kwargs)
class GovcTaskDeptFeedbackBase(TD3iGovcTaskDepartmentFeedback, CommonModel):
"""
部门处置反馈基础类(完全映射 TD3iGovcTaskDepartmentFeedback 字段)。
继承自数据库模型 TD3iGovcTaskDepartmentFeedback 和通用模型 CommonModel。
封装所有与部门处置反馈相关的通用操作方法。
"""
FieldMapping = {
'id': 'id',
'task_id': 'task_id',
'zxhf_info': 'zxhfinfo',
'back_info':'backinfo',
'sign_time_bf': 'signtimebf',
'operation_text': 'operationText',
'opinion': 'opinion',
'unit': 'unit',
'finish_time_bf': 'finishtimebf',
'person': 'person',
'sign_time': 'signtime',
'name': 'name',
'tel': 'tel',
'time': 'time',
'department': 'department',
'status': 'status',
'back_time_bf': 'backtimebf',
}
"""部门处置反馈数据映射"""
@classmethod
async def is_exist(cls, task_id: int):
"""
检查部门处置反馈记录是否已存在(根据工单ID)。
:param task_id: 关联工单主表ID
:return: 存在返回对象,不存在返回None
"""
_query = select(cls).where(cls.task_id == task_id)
_feedback: cls = await cls.query_first(_query)
return _feedback
@classmethod
async def search_base(cls, is_paging=True, **kwargs):
"""
按参数搜索部门处置反馈数据的基础方法。
支持字段:
- task_id, status, unit, department(精确匹配)
- 支持模糊匹配:operation_text, opinion, person, name
- 支持时间范围:sign_time_bf, finish_time_bf, sign_time, time, back_time_bf
:param is_paging: 是否分页
:param kwargs: 查询参数
:key int page_number: 页码(缺省随机1~100
:key int page_size: 每页数量(缺省20
:key dict sort_clause: 排序配置,如 {'task_id': 'asc'}
:key int task_id: 精确匹配工单ID
:key int status: 精确匹配状态
:key str unit: 精确匹配承办单位
:key str department: 精确匹配部门
:key str operation_text: 模糊匹配操作描述
:key str opinion: 模糊匹配反馈意见
:key str person: 模糊匹配承办人
:key str name: 模糊匹配负责人
:key str sign_time_bf_start: 签收时限开始时间
:key str sign_time_bf_end: 签收时限结束时间
:return: (DataFrame, Pagination)
"""
page_number = kwargs.get('page_number', random.randint(1, 100))
page_size = kwargs.get('page_size', 20)
kwargs.update({'page_number': page_number, 'page_size': page_size})
# 模糊查询字段
_name_likes = {
cls.operation_text.key: '%{}%',
cls.opinion.key: '%{}%',
cls.person.key: '%{}%',
cls.name.key: '%{}%',
}
# 构建基础查询
_query = select(cls).where(
*cls.search_wheres(likes=_name_likes, **kwargs)
).group_by(cls.id)
# 处理时间范围查询(示例:签收时限)
if kwargs.get('sign_time_bf_start'):
_query = _query.where(cls.sign_time_bf >= kwargs['sign_time_bf_start'])
if kwargs.get('sign_time_bf_end'):
_query = _query.where(cls.sign_time_bf <= kwargs['sign_time_bf_end'])
_paging = None
if is_paging:
_row_count = await cls.query_count(_query)
_paging = Pagination(_row_count).paging(page_number, page_size)
_data_query = _query.limit(page_size).offset(_paging.offset_size)
else:
_data_query = _query
# 处理排序
_sort_clause = cls.sort_clauses(kwargs.get('sort_clause', {}))
if _sort_clause:
_data_query = _data_query.order_by(*_sort_clause)
else:
_data_query = _data_query.order_by(cls.task_id, cls.id)
# 执行查询并处理结果
_feedback_df = await cls.query_as_df(_data_query)
if not _feedback_df.empty:
_feedback_df.replace(models.EmptyInDF + models.EmptyDatetimeInDF, '', inplace=True)
_feedback_df[cls.id.key] = _feedback_df[cls.id.key].astype(str)
# 处理时间字段格式化
datetime_fields = ['sign_time_bf', 'finish_time_bf', 'sign_time', 'time', 'back_time_bf', 'created_at',
'updated_at']
for field in datetime_fields:
if field in _feedback_df.columns:
_feedback_df[field] = _feedback_df[field].dt.strftime('%Y-%m-%d %H:%M:%S').fillna('')
return _feedback_df, _paging
@classmethod
async def search(cls, **kwargs):
"""
按参数搜索部门处置反馈数据,返回分页格式数据。
"""
_feedback_df, _paging = await cls.search_base(**kwargs)
return {
'total': _paging.row_count,
'rows': _feedback_df.to_dict('records'),
'pagination': {
'page_number': _paging.page_number,
'page_count': _paging.page_count,
'page_size': _paging.page_size,
},
}
@classmethod
async def exists_task_id(cls, data_df: pd.DataFrame):
"""
查找 data_df 中在数据库中已存在和不存在的记录。根据 task_id 字段判断。
:param data_df: 输入的数据框架,必须包含 task_id 列
:return: (exists_df: pd.DataFrame, latest_df: pd.DataFrame)
- exists_df: 在数据库中存在的记录
- latest_df: 在数据库中不存在的记录
"""
if data_df.empty:
return pd.DataFrame(), pd.DataFrame()
# 获取待查询的 task_id 列表(去重)
task_ids = data_df[cls.task_id.key].unique().tolist()
if not task_ids:
return pd.DataFrame(), data_df.copy()
# 查询数据库中已存在的 task_id
_query = select(cls.id, cls.task_id).where(cls.task_id.in_(task_ids))
task_ids_df = await cls.query_as_df(_query)
if task_ids_df.empty:
return pd.DataFrame(), data_df.copy()
# 构建 task_id -> id 的映射字典
task_id_to_id_map = dict(zip(task_ids_df[cls.task_id.key], task_ids_df[cls.id.key]))
# 根据 task_id 是否在数据库中,划分数据
mask_exists = data_df[cls.task_id.key].isin(task_ids_df[cls.task_id.key])
exists_df = data_df[mask_exists].copy()
exists_df[cls.id.key] = exists_df[cls.task_id.key].map(task_id_to_id_map)
latest_df = data_df[~mask_exists].copy()
return exists_df, latest_df
@register_swagger_model
class GovcTaskDeptFeedback(GovcTaskDeptFeedbackBase):
"""
部门处置反馈模型类(主业务类,完全继承 TD3iGovcTaskDepartmentFeedback 字段)。
---
description: 市12345部门处置信息接口
type: object
properties:
id:
description: 主键ID
type: integer
example: 1001
readOnly: true
task_id:
description: 关联工单主表ID
type: integer
example: 20240501001
zxhf_info:
description: 专项回复信息
type: string
example: "该工单已完成处置,符合要求"
sign_time_bf:
description: 签收时限
type: string
format: date-time
example: "2024-05-01 10:00:00"
operation_text:
description: 操作描述
type: string
example: "接收工单并开始处置"
maxLength: 255
opinion:
description: 反馈意见
type: string
example: "经核查,该问题已妥善解决"
unit:
description: 承办单位
type: string
example: "XX市城市管理局"
maxLength: 255
finish_time_bf:
description: 反馈时限
type: string
format: date-time
example: "2024-05-05 18:00:00"
person:
description: 承办人
type: string
example: "张三"
maxLength: 128
sign_time:
description: 签收时间
type: string
format: date-time
example: "2024-05-01 10:10:00"
name:
description: 负责人
type: string
example: "李四"
maxLength: 128
tel:
description: 联系电话
type: string
example: "13800138000"
maxLength: 64
time:
description: 反馈时间
type: string
format: date-time
example: "2024-05-05 17:30:00"
department:
description: 部门
type: string
example: "市容管理科"
maxLength: 255
status:
description: 状态(1:已签收,2:处置中,3:已反馈,4:已拒绝)
type: integer
example: 3
back_time_bf:
description: 拒绝时限
type: string
format: date-time
example: "2024-05-03 18:00:00"
created_at:
description: 创建时间
type: string
format: date-time
example: "2024-05-01 10:00:00"
readOnly: true
created_by:
description: 创建者
type: string
example: "admin"
readOnly: true
updated_at:
description: 修改时间
type: string
format: date-time
example: "2024-05-05 17:30:00"
readOnly: true
updated_by:
description: 修改者
type: string
example: "editor"
readOnly: true
"""
@classmethod
async def create(cls, user: RbacUser = None, **kwargs):
"""
创建新的部门处置反馈记录。
业务流程:
1. 使用 GovcTaskDeptFeedbackForm 验证表单数据完整性
2. 检查是否已存在相同 task_id 的记录(避免重复提交)
3. 创建新反馈对象
4. 设置创建者和更新者为当前用户
5. 保存到数据库
6. 返回创建的对象
:param RbacUser user: 操作用户对象
:param kwargs: 反馈参数字典
:return: 新建反馈对象
:rtype: GovcTaskDeptFeedback
:raises AssertionError: 当记录已存在时抛出
:raises ValidationError: 当表单验证失败时抛出
"""
# 处理字符串字段去除空格
for _k, _v in kwargs.items():
if isinstance(_v, str):
kwargs[_k] = _v.strip()
# 表单验证
_form = GovcTaskDeptFeedbackForm(formdata=kwargs)
_form.validate_form()
# 检查是否已存在相同 task_id 的记录
if _form.task_id.data:
_existing = await cls.is_exist(_form.task_id.data)
assert _existing is None, f"工单ID {_form.task_id.data} 已存在处置反馈记录,不能重复提交。"
# 创建对象
_feedback = cls().copy_from_dict(_form.data, skip_none=True).before_save()
if user:
_feedback.created_by = user.username
_feedback.updated_by = user.username
await _feedback.async_save()
return _feedback
@classmethod
async def delete(cls, feedback_id: Union[str, int]):
"""
删除部门处置反馈记录。
业务流程:
1. 根据ID查找记录
2. 验证存在性
3. 执行删除
:param feedback_id: 要删除的反馈记录ID
:return: 删除的记录对象
:rtype: GovcTaskDeptFeedback
:raises AssertionError: 当记录不存在时抛出
"""
_feedback: cls = await cls.async_find_by_id(feedback_id)
assert _feedback, f"根据 ID {feedback_id} 未找到部门处置反馈记录。"
_del_query = delete(cls).where(cls.id == _feedback.id)
_del_count = (await cls.raw_execute(_del_query)).rowcount
echo_log(f'已删除部门处置反馈记录(工单ID{_feedback.task_id}ID{_feedback.id}.')
return _feedback
@classmethod
async def modify(cls, feedback_id: Union[str, int], user: RbacUser = None, **kwargs):
"""
修改已有部门处置反馈记录。
业务流程:
1. 处理字符串字段去除首尾空格
2. 使用 GovcTaskDeptFeedbackForm 验证表单数据
3. 查询原记录
4. 验证存在性
5. 更新字段并设置更新者
6. 保存到数据库
7. 返回更新后的对象
:param feedback_id: 要修改的反馈记录ID
:param RbacUser user: 操作用户对象
:param kwargs: 需要更新的字段
:return: 修改后的反馈对象
:rtype: GovcTaskDeptFeedback
:raises AssertionError: 当记录不存在时抛出
:raises ValidationError: 当表单验证失败时抛出
"""
# 处理字符串字段去除空格
for _k, _v in kwargs.items():
if isinstance(_v, str):
kwargs[_k] = _v.strip()
# 表单验证
_form = GovcTaskDeptFeedbackForm(formdata=kwargs)
_form.validate_form()
# 查询原记录
_feedback: cls = await cls.async_find_by_id(feedback_id)
assert _feedback, f'查无此部门处置反馈信息。'
# 更新字段
_feedback.copy_from_dict(_form.data, skip_none=True).before_save()
if user:
_feedback.updated_by = user.username
await _feedback.async_save()
return _feedback
@classmethod
async def create_batch(cls, data_df: pd.DataFrame, user: RbacUser = None):
"""
批量创建部门处置反馈记录(传入数据应为全新记录)。
:param data_df: 包含反馈数据的 DataFrame
:param user: 操作用户对象,用于设置 created_by / updated_by
:return: 成功创建的数量
:rtype: int
"""
if data_df.empty:
return 0
# 设置创建者/更新者信息
if user:
data_df['created_by'] = user.username
data_df['updated_by'] = user.username
# 转换为对象列表
records = data_df.to_dict('records')
feedbacks = [cls().copy_from_dict(record, skip_none=True).before_save() for record in records]
# 批量保存
session = cls.get_aio_session()
try:
session.add_all(feedbacks)
await session.commit()
except Exception as e:
await session.rollback()
raise e
finally:
await session.close()
echo_log(f"批量创建成功:创建 {len(feedbacks)} 条部门处置反馈记录。")
return len(feedbacks)
@classmethod
async def modify_batch(cls, data_df: pd.DataFrame, user: RbacUser = None):
"""
批量修改已有部门处置反馈记录。
:param data_df: 包含反馈数据的 DataFrame(必须包含 id 列)
:param user: 操作用户对象,用于设置 updated_by
:return: 成功更新的数量
:rtype: int
"""
if data_df.empty:
return 0
# 必须包含 id 列
if 'id' not in data_df.columns:
echo_log(f"错误:modify_batch 要求输入数据必须包含 '{cls.id.key}' 列(主键)")
return 0
# 设置更新信息
data_df['updated_at'] = datetime.datetime.now()
if user:
data_df['updated_by'] = user.username
# 转换为字典列表
update_data = data_df.to_dict('records')
# 批量更新
session = cls.get_aio_session()
try:
await session.run_sync(
lambda sync_session: sync_session.bulk_update_mappings(cls, update_data)
)
await session.commit()
updated_count = len(update_data)
except Exception as e:
await session.rollback()
raise e
finally:
await session.close()
echo_log(f"批量修改成功:更新 {updated_count} 条部门处置反馈记录。")
return updated_count
@classmethod
async def save_batch(cls, data_df: pd.DataFrame, user: RbacUser = None):
"""
批量保存部门处置反馈数据,自动处理新建和更新。
:param data_df: 要保存的数据框架
:param user: 用户
:return: 新建和更新的数量
"""
# 筛选数据状态(按task_id判断存在性)
_exists_df, _latest_df = await cls.exists_task_id(data_df)
# 批量创建和更新
_created_count = await cls.create_batch(_latest_df, user)
_updated_count = await cls.modify_batch(_exists_df, user)
return _created_count, _updated_count