feat(oa): add instance comment create list and revoke service
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
ea8420b648
commit
c66bfc75c0
2 files changed
+317
No files matched your search
@@ -108,3 +108,183 @@ def normalize_attachment_refs(
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def _fmt_time(dt: Optional[datetime]) -> str:
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S") if dt else ""
|
||||
|
||||
|
||||
def _comment_to_dict(
|
||||
row: OaInstanceComment,
|
||||
*,
|
||||
name_map: Dict[str, str],
|
||||
viewer_user_id: str,
|
||||
instance_status: str,
|
||||
) -> Dict[str, Any]:
|
||||
refs = _loads(row.attachment_refs_json, [])
|
||||
if not isinstance(refs, list):
|
||||
refs = []
|
||||
status = (row.status or "normal").strip() or "normal"
|
||||
can_revoke = (
|
||||
status == "normal"
|
||||
and instance_status == "running"
|
||||
and (row.create_by or "") == (viewer_user_id or "")
|
||||
)
|
||||
return {
|
||||
"id": row.id,
|
||||
"instanceId": row.instance_id,
|
||||
"content": row.content or "",
|
||||
"attachmentRefs": refs,
|
||||
"status": status,
|
||||
"revokedTime": _fmt_time(row.revoked_time),
|
||||
"createBy": row.create_by or "",
|
||||
"createByName": name_map.get(row.create_by or "", row.create_by or ""),
|
||||
"createTime": _fmt_time(row.create_time),
|
||||
"canRevoke": can_revoke,
|
||||
}
|
||||
|
||||
|
||||
def _load_version_schema(db: Session, inst: OaInstance) -> Any:
|
||||
ver = (
|
||||
db.query(OaAppVersion)
|
||||
.filter(OaAppVersion.id == inst.app_version_id, OaAppVersion.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
if not ver:
|
||||
return {}
|
||||
return _loads(ver.form_schema_json, {})
|
||||
|
||||
|
||||
def list_comments(
|
||||
db: Session,
|
||||
*,
|
||||
instance_id: str,
|
||||
tenant_no: str,
|
||||
viewer_user_id: str,
|
||||
) -> List[Dict[str, Any]]:
|
||||
inst = _get_instance(db, instance_id, tenant_no)
|
||||
rows = (
|
||||
db.query(OaInstanceComment)
|
||||
.filter(
|
||||
OaInstanceComment.instance_id == instance_id,
|
||||
OaInstanceComment.tenant_no == tenant_no,
|
||||
OaInstanceComment.del_flag == 0,
|
||||
)
|
||||
.order_by(OaInstanceComment.create_time.asc())
|
||||
.all()
|
||||
)
|
||||
name_map = _user_name_map(db, [r.create_by for r in rows if r.create_by])
|
||||
return [
|
||||
_comment_to_dict(
|
||||
r,
|
||||
name_map=name_map,
|
||||
viewer_user_id=viewer_user_id,
|
||||
instance_status=inst.status or "",
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
|
||||
def list_form_attachments(
|
||||
db: Session, *, instance_id: str, tenant_no: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
inst = _get_instance(db, instance_id, tenant_no)
|
||||
schema = _load_version_schema(db, inst)
|
||||
form = _loads(inst.form_data_json, {})
|
||||
if not isinstance(form, dict):
|
||||
form = {}
|
||||
return enumerate_form_attachments(schema, form)
|
||||
|
||||
|
||||
def create_comment(
|
||||
db: Session,
|
||||
*,
|
||||
instance_id: str,
|
||||
tenant_no: str,
|
||||
user_id: str,
|
||||
dept_id: str,
|
||||
content: str,
|
||||
attachment_refs: Optional[List[Dict[str, Any]]],
|
||||
) -> Dict[str, Any]:
|
||||
inst = _get_instance(db, instance_id, tenant_no)
|
||||
if (inst.status or "") != "running":
|
||||
raise OaEngineError("仅审批进行中可评论")
|
||||
text = (content or "").strip()
|
||||
if not text:
|
||||
raise OaEngineError("评论内容不能为空")
|
||||
if len(text) > CONTENT_MAX:
|
||||
raise OaEngineError(f"评论内容不能超过{CONTENT_MAX}字")
|
||||
schema = _load_version_schema(db, inst)
|
||||
form = _loads(inst.form_data_json, {})
|
||||
if not isinstance(form, dict):
|
||||
form = {}
|
||||
refs = normalize_attachment_refs(schema, form, attachment_refs)
|
||||
now = datetime.now()
|
||||
row = OaInstanceComment(
|
||||
id=generate_uuid(),
|
||||
instance_id=instance_id,
|
||||
content=text,
|
||||
attachment_refs_json=json.dumps(refs, ensure_ascii=False) if refs else None,
|
||||
status="normal",
|
||||
revoked_time=None,
|
||||
tenant_no=tenant_no,
|
||||
create_by=user_id,
|
||||
create_dept_id=dept_id or "",
|
||||
create_time=now,
|
||||
update_by=user_id,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
name_map = _user_name_map(db, [user_id])
|
||||
return _comment_to_dict(
|
||||
row,
|
||||
name_map=name_map,
|
||||
viewer_user_id=user_id,
|
||||
instance_status=inst.status or "",
|
||||
)
|
||||
|
||||
|
||||
def revoke_comment(
|
||||
db: Session,
|
||||
*,
|
||||
instance_id: str,
|
||||
comment_id: str,
|
||||
tenant_no: str,
|
||||
user_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
inst = _get_instance(db, instance_id, tenant_no)
|
||||
if (inst.status or "") != "running":
|
||||
raise OaEngineError("仅审批进行中可撤回评论")
|
||||
row = (
|
||||
db.query(OaInstanceComment)
|
||||
.filter(
|
||||
OaInstanceComment.id == comment_id,
|
||||
OaInstanceComment.instance_id == instance_id,
|
||||
OaInstanceComment.tenant_no == tenant_no,
|
||||
OaInstanceComment.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
raise OaEngineError("评论不存在")
|
||||
if (row.create_by or "") != (user_id or ""):
|
||||
raise OaEngineError("只能撤回自己的评论")
|
||||
now = datetime.now()
|
||||
if (row.status or "") != "revoked":
|
||||
row.status = "revoked"
|
||||
row.revoked_time = now
|
||||
row.update_by = user_id
|
||||
row.update_time = now
|
||||
db.add(row)
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
name_map = _user_name_map(db, [row.create_by] if row.create_by else [])
|
||||
return _comment_to_dict(
|
||||
row,
|
||||
name_map=name_map,
|
||||
viewer_user_id=user_id,
|
||||
instance_status=inst.status or "",
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
"""OA 实例评论:附件枚举与引用校验"""
|
||||
import json
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from app.services.oa_instance_comment import (
|
||||
enumerate_form_attachments,
|
||||
@@ -61,3 +63,138 @@ class TestNormalizeAttachmentRefs(unittest.TestCase):
|
||||
form,
|
||||
[{"fieldKey": "files", "url": "https://evil/x.pdf"}],
|
||||
)
|
||||
|
||||
|
||||
class TestCommentCrud(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.models.models import OaAppVersion, OaInstance, OaInstanceComment, SysUser
|
||||
|
||||
self.engine = create_engine("sqlite:///:memory:")
|
||||
OaAppVersion.__table__.create(self.engine)
|
||||
OaInstance.__table__.create(self.engine)
|
||||
OaInstanceComment.__table__.create(self.engine)
|
||||
SysUser.__table__.create(self.engine)
|
||||
Session = sessionmaker(bind=self.engine, autoflush=False, autocommit=False)
|
||||
self.db = Session()
|
||||
now = datetime.now()
|
||||
schema = {
|
||||
"rule": [
|
||||
{"type": "upload", "field": "files", "title": "附件"},
|
||||
]
|
||||
}
|
||||
self.db.add(
|
||||
OaAppVersion(
|
||||
id="v1",
|
||||
app_id="app1",
|
||||
version_no=1,
|
||||
status="published",
|
||||
is_latest_published="1",
|
||||
form_schema_json=json.dumps(schema, ensure_ascii=False),
|
||||
flow_tree_json="{}",
|
||||
advanced_settings_json="{}",
|
||||
tenant_no="zkxw",
|
||||
create_dept_id="",
|
||||
create_time=now,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
)
|
||||
)
|
||||
self.db.add(
|
||||
OaInstance(
|
||||
id="i1",
|
||||
app_id="app1",
|
||||
app_version_id="v1",
|
||||
title="t",
|
||||
status="running",
|
||||
initiator_user_id="u0",
|
||||
initiator_dept_id="d1",
|
||||
form_data_json=json.dumps(
|
||||
{"files": [{"url": "https://x/a.pdf", "name": "a.pdf"}]},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
tenant_no="zkxw",
|
||||
create_dept_id="d1",
|
||||
create_time=now,
|
||||
update_time=now,
|
||||
del_flag=0,
|
||||
current_round=1,
|
||||
)
|
||||
)
|
||||
self.db.add(
|
||||
SysUser(
|
||||
id="u1",
|
||||
tenant_no="zkxw",
|
||||
user_no="u1",
|
||||
nick_name="张三",
|
||||
email="",
|
||||
phone_no="",
|
||||
password="",
|
||||
status="1",
|
||||
create_dept_id="",
|
||||
del_flag=0,
|
||||
create_time=now,
|
||||
update_time=now,
|
||||
)
|
||||
)
|
||||
self.db.commit()
|
||||
|
||||
def test_create_list_revoke(self):
|
||||
from app.services import oa_instance_comment as svc
|
||||
|
||||
row = svc.create_comment(
|
||||
self.db,
|
||||
instance_id="i1",
|
||||
tenant_no="zkxw",
|
||||
user_id="u1",
|
||||
dept_id="d1",
|
||||
content="请看附件",
|
||||
attachment_refs=[{"fieldKey": "files", "url": "https://x/a.pdf"}],
|
||||
)
|
||||
self.assertEqual(row["status"], "normal")
|
||||
self.assertTrue(row["canRevoke"])
|
||||
self.assertEqual(len(row["attachmentRefs"]), 1)
|
||||
|
||||
listed = svc.list_comments(
|
||||
self.db, instance_id="i1", tenant_no="zkxw", viewer_user_id="u1"
|
||||
)
|
||||
self.assertEqual(len(listed), 1)
|
||||
|
||||
revoked = svc.revoke_comment(
|
||||
self.db,
|
||||
instance_id="i1",
|
||||
comment_id=row["id"],
|
||||
tenant_no="zkxw",
|
||||
user_id="u1",
|
||||
)
|
||||
self.assertEqual(revoked["status"], "revoked")
|
||||
self.assertFalse(revoked["canRevoke"])
|
||||
|
||||
again = svc.revoke_comment(
|
||||
self.db,
|
||||
instance_id="i1",
|
||||
comment_id=row["id"],
|
||||
tenant_no="zkxw",
|
||||
user_id="u1",
|
||||
)
|
||||
self.assertEqual(again["status"], "revoked")
|
||||
|
||||
def test_cannot_comment_when_not_running(self):
|
||||
from app.services import oa_instance_comment as svc
|
||||
from app.services.oa_engine import OaEngineError
|
||||
from app.models.models import OaInstance
|
||||
|
||||
inst = self.db.query(OaInstance).filter(OaInstance.id == "i1").one()
|
||||
inst.status = "completed"
|
||||
self.db.commit()
|
||||
with self.assertRaises(OaEngineError):
|
||||
svc.create_comment(
|
||||
self.db,
|
||||
instance_id="i1",
|
||||
tenant_no="zkxw",
|
||||
user_id="u1",
|
||||
dept_id="d1",
|
||||
content="x",
|
||||
attachment_refs=None,
|
||||
)
|
||||
Reference in new issue
Block a user