937 lines
31 KiB
Python
937 lines
31 KiB
Python
"""移动市场协议服务:双来源统一展示,写入保持来源内原子性。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import List, Optional, Tuple
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import func, or_
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.utils import parse_datetime
|
|
from app.crud import collection_protocol as cell_crud
|
|
from app.crud import research_donation as donation_crud
|
|
from app.models.models import (
|
|
BizCellSampleType,
|
|
BizCollectionHospital,
|
|
BizCollectionProtocol,
|
|
BizCollectionRegion,
|
|
BizCollectionRegionStaff,
|
|
BizCustomer,
|
|
BizItemUnit,
|
|
BizProtocolCollectionInfo,
|
|
BizProtocolPartyCustomer,
|
|
BizProtocolSample,
|
|
BizSampleCode,
|
|
SysUser,
|
|
)
|
|
from app.models.research_donation import (
|
|
BizResearchDonation,
|
|
BizResearchDonationCollection,
|
|
BizResearchDonationParty,
|
|
BizResearchDonationSample,
|
|
)
|
|
from app.schemas.market_mobile import MarketProtocolBody
|
|
from app.services.market_field_scope import (
|
|
MarketFieldContext,
|
|
apply_cell_protocol_scope,
|
|
apply_donation_scope,
|
|
assert_cell_protocol_access,
|
|
assert_donation_access,
|
|
)
|
|
from app.services.market_support import validate_attachment_refs
|
|
|
|
SOURCE_CELL = 'cell_storage'
|
|
SOURCE_DONATION = 'research_donation'
|
|
SOURCES = (SOURCE_CELL, SOURCE_DONATION)
|
|
_CUSTOMER_LINK_FIELDS = (
|
|
'customer_id',
|
|
'father_customer_id',
|
|
'newborn_customer_id',
|
|
)
|
|
|
|
|
|
def _current_dept_id(user_info: dict) -> str:
|
|
if not isinstance(user_info, dict):
|
|
return ''
|
|
return str(
|
|
user_info.get('dept_id')
|
|
or user_info.get('deptId')
|
|
or user_info.get('region_id')
|
|
or ''
|
|
).strip()
|
|
|
|
|
|
def _dump(value) -> dict:
|
|
if value is None:
|
|
return {}
|
|
if hasattr(value, 'model_dump'):
|
|
return value.model_dump(by_alias=False, exclude_unset=True)
|
|
return dict(value)
|
|
|
|
|
|
def _assert_customer_link_policy(
|
|
customer: dict, *, current_party=None, creating: bool
|
|
) -> None:
|
|
"""移动端不创建/改挂客户主档,只保留单据原有客户关联。"""
|
|
for key in _CUSTOMER_LINK_FIELDS:
|
|
if key not in customer:
|
|
continue
|
|
supplied = str(customer.get(key) or '').strip()
|
|
if creating:
|
|
if supplied:
|
|
raise ValueError('移动端创建协议不允许指定已有客户')
|
|
continue
|
|
current = str(getattr(current_party, key, None) or '').strip()
|
|
if supplied != current:
|
|
raise ValueError('客户关联不属于当前协议,禁止改挂')
|
|
|
|
|
|
def _fmt_dt(value) -> str:
|
|
if value is None:
|
|
return ''
|
|
if isinstance(value, str):
|
|
return value
|
|
return value.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
def _manager_assignment_allowed(
|
|
db: Session,
|
|
ctx: MarketFieldContext,
|
|
*,
|
|
region_id: Optional[str],
|
|
region_manager_user_id: Optional[str],
|
|
) -> bool:
|
|
if str(region_manager_user_id or '').strip() == ctx.user_id:
|
|
return True
|
|
region = str(region_id or '').strip()
|
|
if not region:
|
|
return False
|
|
return (
|
|
db.query(BizCollectionRegionStaff.id)
|
|
.filter(
|
|
BizCollectionRegionStaff.tenant_no == ctx.tenant_no,
|
|
BizCollectionRegionStaff.del_flag == 0,
|
|
BizCollectionRegionStaff.user_id == ctx.user_id,
|
|
BizCollectionRegionStaff.is_leader == 1,
|
|
BizCollectionRegionStaff.region_id == region,
|
|
)
|
|
.first()
|
|
is not None
|
|
)
|
|
|
|
|
|
def _ensure_manager_assignment(
|
|
db: Session,
|
|
ctx: MarketFieldContext,
|
|
*,
|
|
region_id: Optional[str],
|
|
region_manager_user_id: Optional[str],
|
|
) -> None:
|
|
if not _manager_assignment_allowed(
|
|
db,
|
|
ctx,
|
|
region_id=region_id,
|
|
region_manager_user_id=region_manager_user_id,
|
|
):
|
|
raise ValueError('协议归属不在当前地区经理范围内')
|
|
|
|
|
|
def _active_user_ids(db: Session, ctx: MarketFieldContext, ids: set) -> set:
|
|
if not ids:
|
|
return set()
|
|
return {
|
|
str(row[0])
|
|
for row in db.query(SysUser.id)
|
|
.filter(
|
|
SysUser.id.in_(ids),
|
|
SysUser.tenant_no == ctx.tenant_no,
|
|
SysUser.del_flag == 0,
|
|
SysUser.status == '1',
|
|
)
|
|
.all()
|
|
}
|
|
|
|
|
|
def _validate_references(
|
|
db: Session,
|
|
ctx: MarketFieldContext,
|
|
*,
|
|
customer: dict,
|
|
collection: dict,
|
|
samples: List[dict],
|
|
extra_user_ids: Optional[set] = None,
|
|
) -> None:
|
|
region_id = str(collection.get('region_id') or '').strip()
|
|
hospital_id = str(collection.get('hospital_id') or '').strip()
|
|
if region_id:
|
|
exists = (
|
|
db.query(BizCollectionRegion.id)
|
|
.filter(
|
|
BizCollectionRegion.id == region_id,
|
|
BizCollectionRegion.tenant_no == ctx.tenant_no,
|
|
BizCollectionRegion.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not exists:
|
|
raise ValueError('采集地区不存在或不属于当前租户')
|
|
if hospital_id:
|
|
exists = (
|
|
db.query(BizCollectionHospital.id)
|
|
.filter(
|
|
BizCollectionHospital.id == hospital_id,
|
|
BizCollectionHospital.region_id == region_id,
|
|
BizCollectionHospital.tenant_no == ctx.tenant_no,
|
|
BizCollectionHospital.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
if not exists:
|
|
raise ValueError('医院不存在、不属于当前租户或与采集地区不匹配')
|
|
|
|
user_ids = {
|
|
str(value).strip()
|
|
for value in (
|
|
customer.get('collector_user_id'),
|
|
collection.get('sales_user_id'),
|
|
collection.get('region_manager_user_id'),
|
|
)
|
|
if str(value or '').strip()
|
|
}
|
|
user_ids.update(
|
|
str(value).strip()
|
|
for value in (extra_user_ids or set())
|
|
if str(value or '').strip()
|
|
)
|
|
if _active_user_ids(db, ctx, user_ids) != user_ids:
|
|
raise ValueError('引用用户不存在、已停用或不属于当前租户')
|
|
|
|
customer_ids = {
|
|
str(customer.get(key) or '').strip()
|
|
for key in ('customer_id', 'father_customer_id', 'newborn_customer_id')
|
|
if str(customer.get(key) or '').strip()
|
|
}
|
|
if customer_ids:
|
|
existing = {
|
|
str(row[0])
|
|
for row in db.query(BizCustomer.id)
|
|
.filter(
|
|
BizCustomer.id.in_(customer_ids),
|
|
BizCustomer.tenant_no == ctx.tenant_no,
|
|
BizCustomer.del_flag == 0,
|
|
)
|
|
.all()
|
|
}
|
|
if existing != customer_ids:
|
|
raise ValueError('客户不存在或不属于当前租户')
|
|
|
|
type_ids = {
|
|
str(item.get('sample_type_id') or '').strip()
|
|
for item in samples
|
|
if str(item.get('sample_type_id') or '').strip()
|
|
}
|
|
if type_ids:
|
|
existing = {
|
|
str(row[0])
|
|
for row in db.query(BizCellSampleType.id)
|
|
.filter(
|
|
BizCellSampleType.id.in_(type_ids),
|
|
BizCellSampleType.tenant_no == ctx.tenant_no,
|
|
BizCellSampleType.del_flag == 0,
|
|
)
|
|
.all()
|
|
}
|
|
if existing != type_ids:
|
|
raise ValueError('样本类型不存在或不属于当前租户')
|
|
unit_ids = {
|
|
str(item.get('unit_id') or '').strip()
|
|
for item in samples
|
|
if str(item.get('unit_id') or '').strip()
|
|
}
|
|
if unit_ids:
|
|
existing = {
|
|
str(row[0])
|
|
for row in db.query(BizItemUnit.id)
|
|
.filter(
|
|
BizItemUnit.id.in_(unit_ids),
|
|
BizItemUnit.tenant_no == ctx.tenant_no,
|
|
BizItemUnit.del_flag == 0,
|
|
)
|
|
.all()
|
|
}
|
|
if existing != unit_ids:
|
|
raise ValueError('计量单位不存在或不属于当前租户')
|
|
|
|
sample_code_ids = {
|
|
str(item.get('sample_code_id') or '').strip()
|
|
for item in samples
|
|
if str(item.get('sample_code_id') or '').strip()
|
|
}
|
|
sample_code_map = {}
|
|
if sample_code_ids:
|
|
sample_code_map = {
|
|
str(row.id): str(row.sample_code or '').strip()
|
|
for row in db.query(BizSampleCode)
|
|
.filter(
|
|
BizSampleCode.id.in_(sample_code_ids),
|
|
BizSampleCode.tenant_no == ctx.tenant_no,
|
|
BizSampleCode.del_flag == 0,
|
|
)
|
|
.all()
|
|
}
|
|
if set(sample_code_map) != sample_code_ids:
|
|
raise ValueError('样本编码不存在、已删除或不属于当前租户')
|
|
for item in samples:
|
|
sample_code_id = str(item.get('sample_code_id') or '').strip()
|
|
if not sample_code_id:
|
|
continue
|
|
expected_code = sample_code_map[sample_code_id]
|
|
supplied_code = str(item.get('sample_code') or '').strip()
|
|
if supplied_code and supplied_code != expected_code:
|
|
raise ValueError('样本编码ID与样本编码不一致')
|
|
item['sample_code'] = expected_code
|
|
|
|
|
|
def _normalize_write(
|
|
db: Session,
|
|
*,
|
|
body: MarketProtocolBody,
|
|
ctx: MarketFieldContext,
|
|
current_collection=None,
|
|
) -> Tuple[dict, dict, List[dict], dict]:
|
|
customer = _dump(body.customer)
|
|
collection = _dump(body.collection)
|
|
samples = [_dump(item) for item in (body.samples or [])]
|
|
attachments = _dump(body.attachments)
|
|
effective_collection = {
|
|
key: getattr(current_collection, key, None)
|
|
for key in (
|
|
'region_id',
|
|
'hospital_id',
|
|
'sales_user_id',
|
|
'region_manager_user_id',
|
|
)
|
|
}
|
|
effective_collection.update(collection)
|
|
if ctx.scope_kind == 'specialist':
|
|
customer['collector_user_id'] = ctx.user_id
|
|
collection['sales_user_id'] = ctx.user_id
|
|
effective_collection['sales_user_id'] = ctx.user_id
|
|
else:
|
|
_ensure_manager_assignment(
|
|
db,
|
|
ctx,
|
|
region_id=effective_collection.get('region_id'),
|
|
region_manager_user_id=effective_collection.get(
|
|
'region_manager_user_id'
|
|
),
|
|
)
|
|
for key in ('sample_collect_time', 'maternal_blood_collect_time'):
|
|
if key in collection and isinstance(collection[key], str):
|
|
collection[key] = parse_datetime(collection[key])
|
|
if 'newborn_delivery_time' in customer and isinstance(
|
|
customer['newborn_delivery_time'], str
|
|
):
|
|
customer['newborn_delivery_time'] = parse_datetime(
|
|
customer['newborn_delivery_time']
|
|
)
|
|
_validate_references(
|
|
db,
|
|
ctx,
|
|
customer=customer,
|
|
collection=effective_collection,
|
|
samples=samples,
|
|
extra_user_ids={body.collector_user_id}
|
|
if ctx.scope_kind == 'manager' and body.collector_user_id
|
|
else set(),
|
|
)
|
|
return customer, collection, samples, attachments
|
|
|
|
|
|
def _validate_mobile_attachments(
|
|
db: Session,
|
|
attachments: dict,
|
|
*,
|
|
ctx: MarketFieldContext,
|
|
creating: bool,
|
|
existing: Optional[dict] = None,
|
|
business_id: Optional[str] = None,
|
|
) -> dict:
|
|
current = existing or {}
|
|
normalized = dict(attachments)
|
|
for key, refs in attachments.items():
|
|
if refs is None:
|
|
continue
|
|
normalized[key] = validate_attachment_refs(
|
|
db,
|
|
refs,
|
|
module='protocol',
|
|
ctx=ctx,
|
|
creating=creating,
|
|
existing_refs=current.get(key),
|
|
business_id=business_id,
|
|
)
|
|
return normalized
|
|
|
|
|
|
def _party_name_cell(db: Session, ids: List[str], tenant_no: str) -> dict:
|
|
if not ids:
|
|
return {}
|
|
rows = (
|
|
db.query(BizProtocolPartyCustomer)
|
|
.filter(
|
|
BizProtocolPartyCustomer.collection_protocol_id.in_(ids),
|
|
BizProtocolPartyCustomer.tenant_no == tenant_no,
|
|
BizProtocolPartyCustomer.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
return {row.collection_protocol_id: row.mother_name or '' for row in rows}
|
|
|
|
|
|
def _party_name_donation(db: Session, ids: List[str], tenant_no: str) -> dict:
|
|
if not ids:
|
|
return {}
|
|
rows = (
|
|
db.query(BizResearchDonationParty)
|
|
.filter(
|
|
BizResearchDonationParty.research_donation_id.in_(ids),
|
|
BizResearchDonationParty.tenant_no == tenant_no,
|
|
BizResearchDonationParty.del_flag == 0,
|
|
)
|
|
.all()
|
|
)
|
|
return {row.research_donation_id: row.mother_name or '' for row in rows}
|
|
|
|
|
|
def list_protocols(
|
|
db: Session,
|
|
*,
|
|
ctx: MarketFieldContext,
|
|
user_info: dict,
|
|
page: int = 1,
|
|
page_size: int = 10,
|
|
keyword: Optional[str] = None,
|
|
source: Optional[str] = None,
|
|
) -> Tuple[List[dict], int]:
|
|
if source and source not in SOURCES:
|
|
raise ValueError('不支持的协议来源')
|
|
search = str(keyword or '').strip()
|
|
rows = []
|
|
fetch_limit = max(1, page) * min(max(1, page_size), 100)
|
|
total = 0
|
|
if source in (None, SOURCE_CELL):
|
|
query = apply_cell_protocol_scope(
|
|
db.query(BizCollectionProtocol), db, ctx, user_info=user_info
|
|
)
|
|
if search:
|
|
query = query.filter(BizCollectionProtocol.protocol_no.like(f'%{search}%'))
|
|
total += int(
|
|
query.order_by(None)
|
|
.with_entities(func.count(BizCollectionProtocol.id))
|
|
.scalar()
|
|
or 0
|
|
)
|
|
cell_rows = (
|
|
query.order_by(
|
|
BizCollectionProtocol.create_time.desc(),
|
|
BizCollectionProtocol.id.desc(),
|
|
)
|
|
.limit(fetch_limit)
|
|
.all()
|
|
)
|
|
names = _party_name_cell(
|
|
db, [row.id for row in cell_rows], ctx.tenant_no
|
|
)
|
|
rows.extend(
|
|
{
|
|
'id': row.id,
|
|
'source': SOURCE_CELL,
|
|
'code': row.protocol_no or '',
|
|
'protocolNo': row.protocol_no or '',
|
|
'status': row.protocol_status or '',
|
|
'customerName': names.get(row.id, ''),
|
|
'createTime': _fmt_dt(row.create_time),
|
|
'_created': row.create_time or datetime.min,
|
|
}
|
|
for row in cell_rows
|
|
)
|
|
if source in (None, SOURCE_DONATION):
|
|
query = apply_donation_scope(
|
|
db.query(BizResearchDonation), db, ctx, user_info=user_info
|
|
)
|
|
if search:
|
|
query = query.filter(BizResearchDonation.donation_no.like(f'%{search}%'))
|
|
total += int(
|
|
query.order_by(None)
|
|
.with_entities(func.count(BizResearchDonation.id))
|
|
.scalar()
|
|
or 0
|
|
)
|
|
donation_rows = (
|
|
query.order_by(
|
|
BizResearchDonation.create_time.desc(),
|
|
BizResearchDonation.id.desc(),
|
|
)
|
|
.limit(fetch_limit)
|
|
.all()
|
|
)
|
|
names = _party_name_donation(
|
|
db, [row.id for row in donation_rows], ctx.tenant_no
|
|
)
|
|
rows.extend(
|
|
{
|
|
'id': row.id,
|
|
'source': SOURCE_DONATION,
|
|
'code': row.donation_no or '',
|
|
'protocolNo': row.donation_no or '',
|
|
'status': row.donation_status or '',
|
|
'customerName': names.get(row.id, ''),
|
|
'createTime': _fmt_dt(row.create_time),
|
|
'_created': row.create_time or datetime.min,
|
|
}
|
|
for row in donation_rows
|
|
)
|
|
rows.sort(
|
|
key=lambda item: (item['_created'], item['id']), reverse=True
|
|
)
|
|
safe_page = max(1, page)
|
|
safe_size = min(max(1, page_size), 100)
|
|
page_rows = rows[(safe_page - 1) * safe_size : safe_page * safe_size]
|
|
for item in page_rows:
|
|
item.pop('_created', None)
|
|
return page_rows, total
|
|
|
|
|
|
def _cell_detail(db: Session, row: BizCollectionProtocol, tenant_no: str) -> dict:
|
|
party = cell_crud.get_party_customer(db, row.id, tenant_no)
|
|
collection = cell_crud.get_collection_info(db, row.id, tenant_no)
|
|
samples = cell_crud.list_samples(db, row.id, tenant_no)
|
|
payment = cell_crud.get_payment_by_protocol_id(db, row.id, tenant_no)
|
|
return {
|
|
'source': SOURCE_CELL,
|
|
'basic': cell_crud._protocol_to_dict(row),
|
|
'collection': cell_crud._collection_info_to_dict(collection)
|
|
if collection
|
|
else None,
|
|
'customer': cell_crud._party_customer_to_dict(party) if party else None,
|
|
'samples': [cell_crud._sample_to_dict(item) for item in samples],
|
|
'payment': cell_crud._payment_to_dict(payment) if payment else None,
|
|
'attachments': {
|
|
'protocolAttachments': row.protocol_attachments or '',
|
|
'consentFormUrls': row.consent_form_urls or '',
|
|
'healthSurveyFormUrls': row.health_survey_form_urls or '',
|
|
},
|
|
}
|
|
|
|
|
|
def _donation_detail(
|
|
db: Session, row: BizResearchDonation, tenant_no: str
|
|
) -> dict:
|
|
party = donation_crud.get_party(db, row.id, tenant_no)
|
|
collection = donation_crud.get_collection(db, row.id, tenant_no)
|
|
samples = donation_crud.list_samples(db, row.id, tenant_no)
|
|
return {
|
|
'source': SOURCE_DONATION,
|
|
'basic': donation_crud._donation_to_dict(row),
|
|
'collection': donation_crud._collection_to_dict(collection)
|
|
if collection
|
|
else None,
|
|
'customer': donation_crud._party_to_dict(party) if party else None,
|
|
'samples': [donation_crud._sample_to_dict(item) for item in samples],
|
|
'attachments': {
|
|
'consentFormUrls': row.consent_form_urls or '',
|
|
'attachmentUrls': row.attachment_urls or '',
|
|
'infectiousDiseaseReportUrls': row.infectious_disease_report_urls
|
|
or '',
|
|
},
|
|
}
|
|
|
|
|
|
def get_protocol(
|
|
db: Session,
|
|
*,
|
|
source: Optional[str],
|
|
protocol_id: str,
|
|
ctx: MarketFieldContext,
|
|
user_info: dict,
|
|
) -> dict:
|
|
if source == SOURCE_CELL:
|
|
row = assert_cell_protocol_access(
|
|
db, protocol_id, ctx, user_info=user_info
|
|
)
|
|
return _cell_detail(db, row, ctx.tenant_no)
|
|
if source == SOURCE_DONATION:
|
|
row = assert_donation_access(db, protocol_id, ctx, user_info=user_info)
|
|
return _donation_detail(db, row, ctx.tenant_no)
|
|
if source is None:
|
|
cell = (
|
|
apply_cell_protocol_scope(
|
|
db.query(BizCollectionProtocol), db, ctx, user_info=user_info
|
|
)
|
|
.filter(BizCollectionProtocol.id == protocol_id)
|
|
.first()
|
|
)
|
|
if cell is not None:
|
|
return _cell_detail(db, cell, ctx.tenant_no)
|
|
donation = (
|
|
apply_donation_scope(
|
|
db.query(BizResearchDonation), db, ctx, user_info=user_info
|
|
)
|
|
.filter(BizResearchDonation.id == protocol_id)
|
|
.first()
|
|
)
|
|
if donation is not None:
|
|
return _donation_detail(db, donation, ctx.tenant_no)
|
|
raise HTTPException(status_code=404, detail='数据不存在或无权访问')
|
|
raise ValueError('不支持的协议来源')
|
|
|
|
|
|
def create_protocol(
|
|
db: Session,
|
|
*,
|
|
body: MarketProtocolBody,
|
|
ctx: MarketFieldContext,
|
|
user_info: dict,
|
|
) -> dict:
|
|
customer, collection, samples, attachments = _normalize_write(
|
|
db, body=body, ctx=ctx
|
|
)
|
|
_assert_customer_link_policy(customer, creating=True)
|
|
dept_id = _current_dept_id(user_info)
|
|
try:
|
|
if body.source == SOURCE_CELL:
|
|
row = cell_crud.create_collection_protocol(
|
|
db,
|
|
protocol_no=body.protocol_no or '',
|
|
protocol_attachments=attachments.get('protocol_attachments'),
|
|
consent_form_urls=attachments.get('consent_form_urls'),
|
|
health_survey_form_urls=attachments.get(
|
|
'health_survey_form_urls'
|
|
),
|
|
register_date=body.register_date,
|
|
collector_user_id=ctx.user_id
|
|
if ctx.scope_kind == 'specialist'
|
|
else body.collector_user_id,
|
|
protocol_total_amount=body.protocol_total_amount,
|
|
remark=body.remark,
|
|
tenant_no=ctx.tenant_no,
|
|
user_id=ctx.user_id,
|
|
create_dept_id=dept_id,
|
|
commit=False,
|
|
)
|
|
attachments = _validate_mobile_attachments(
|
|
db,
|
|
attachments,
|
|
ctx=ctx,
|
|
creating=True,
|
|
business_id=row.id,
|
|
)
|
|
row.protocol_attachments = attachments.get('protocol_attachments')
|
|
row.consent_form_urls = attachments.get('consent_form_urls')
|
|
row.health_survey_form_urls = attachments.get(
|
|
'health_survey_form_urls'
|
|
)
|
|
db.add(row)
|
|
cell_crud.upsert_party_customer(
|
|
db,
|
|
row.id,
|
|
ctx.tenant_no,
|
|
ctx.user_id,
|
|
commit=False,
|
|
create_dept_id=dept_id,
|
|
**customer,
|
|
)
|
|
cell_crud.upsert_collection_info(
|
|
db,
|
|
row.id,
|
|
ctx.tenant_no,
|
|
ctx.user_id,
|
|
commit=False,
|
|
create_dept_id=dept_id,
|
|
**collection,
|
|
)
|
|
cell_crud.save_samples(
|
|
db,
|
|
row.id,
|
|
ctx.tenant_no,
|
|
ctx.user_id,
|
|
samples,
|
|
create_dept_id=dept_id,
|
|
commit=False,
|
|
)
|
|
assert_cell_protocol_access(db, row.id, ctx, user_info=user_info)
|
|
db.commit()
|
|
db.refresh(row)
|
|
return _cell_detail(db, row, ctx.tenant_no)
|
|
|
|
data = {
|
|
'donation_no': body.donation_no or '',
|
|
'donation_date': body.donation_date,
|
|
'collector_user_id': ctx.user_id
|
|
if ctx.scope_kind == 'specialist'
|
|
else body.collector_user_id,
|
|
'remark': body.remark,
|
|
'party': customer,
|
|
'collection': collection,
|
|
'samples': samples,
|
|
'consent_form_urls': attachments.get('consent_form_urls'),
|
|
'attachment_urls': attachments.get('attachment_urls'),
|
|
'infectious_disease_report_urls': attachments.get(
|
|
'infectious_disease_report_urls'
|
|
),
|
|
}
|
|
row = donation_crud.create_donation(
|
|
db,
|
|
data=data,
|
|
tenant_no=ctx.tenant_no,
|
|
user_id=ctx.user_id,
|
|
create_dept_id=dept_id,
|
|
commit=False,
|
|
sync_customers=False,
|
|
)
|
|
attachments = _validate_mobile_attachments(
|
|
db,
|
|
attachments,
|
|
ctx=ctx,
|
|
creating=True,
|
|
business_id=row.id,
|
|
)
|
|
row.consent_form_urls = attachments.get('consent_form_urls')
|
|
row.attachment_urls = attachments.get('attachment_urls')
|
|
row.infectious_disease_report_urls = attachments.get(
|
|
'infectious_disease_report_urls'
|
|
)
|
|
db.add(row)
|
|
assert_donation_access(db, row.id, ctx, user_info=user_info)
|
|
db.commit()
|
|
db.refresh(row)
|
|
return _donation_detail(db, row, ctx.tenant_no)
|
|
except Exception:
|
|
db.rollback()
|
|
raise
|
|
|
|
|
|
def update_protocol(
|
|
db: Session,
|
|
*,
|
|
source: str,
|
|
protocol_id: str,
|
|
body: MarketProtocolBody,
|
|
ctx: MarketFieldContext,
|
|
user_info: dict,
|
|
) -> dict:
|
|
if body.source != source:
|
|
raise ValueError('请求来源与路径来源不一致')
|
|
dept_id = _current_dept_id(user_info)
|
|
try:
|
|
if source == SOURCE_CELL:
|
|
row = assert_cell_protocol_access(
|
|
db, protocol_id, ctx, user_info=user_info
|
|
)
|
|
if (row.protocol_status or '').strip() not in (
|
|
'',
|
|
'draft',
|
|
'dsx',
|
|
'dqr',
|
|
'sxz',
|
|
'ygq',
|
|
):
|
|
raise ValueError('当前状态的协议不可修改')
|
|
current_collection = cell_crud.get_collection_info(
|
|
db, row.id, ctx.tenant_no
|
|
)
|
|
current_party = cell_crud.get_party_customer(
|
|
db, row.id, ctx.tenant_no
|
|
)
|
|
customer, collection, samples, attachments = _normalize_write(
|
|
db,
|
|
body=body,
|
|
ctx=ctx,
|
|
current_collection=current_collection,
|
|
)
|
|
attachments = _validate_mobile_attachments(
|
|
db,
|
|
attachments,
|
|
ctx=ctx,
|
|
creating=False,
|
|
existing={
|
|
'protocol_attachments': row.protocol_attachments,
|
|
'consent_form_urls': row.consent_form_urls,
|
|
'health_survey_form_urls': row.health_survey_form_urls,
|
|
},
|
|
business_id=row.id,
|
|
)
|
|
_assert_customer_link_policy(
|
|
customer, current_party=current_party, creating=False
|
|
)
|
|
cell_crud.update_collection_protocol(
|
|
db,
|
|
row.id,
|
|
tenant_no=ctx.tenant_no,
|
|
user_id=ctx.user_id,
|
|
protocol_no=body.protocol_no,
|
|
protocol_attachments=attachments.get('protocol_attachments'),
|
|
consent_form_urls=attachments.get('consent_form_urls'),
|
|
health_survey_form_urls=attachments.get(
|
|
'health_survey_form_urls'
|
|
),
|
|
register_date=body.register_date,
|
|
collector_user_id=ctx.user_id
|
|
if ctx.scope_kind == 'specialist'
|
|
else body.collector_user_id,
|
|
protocol_total_amount=body.protocol_total_amount,
|
|
remark=body.remark,
|
|
commit=False,
|
|
)
|
|
if body.customer is not None:
|
|
if current_party is not None:
|
|
customer = {
|
|
**cell_crud._normalize_fields(
|
|
cell_crud._party_customer_to_dict(current_party)
|
|
),
|
|
**customer,
|
|
}
|
|
for metadata_key in (
|
|
'id',
|
|
'collection_protocol_id',
|
|
'tenant_no',
|
|
'order_num',
|
|
'create_time',
|
|
'update_time',
|
|
):
|
|
customer.pop(metadata_key, None)
|
|
newborn_delivery_time = customer.get(
|
|
'newborn_delivery_time'
|
|
)
|
|
if isinstance(newborn_delivery_time, str):
|
|
if newborn_delivery_time.strip():
|
|
customer['newborn_delivery_time'] = parse_datetime(
|
|
newborn_delivery_time
|
|
)
|
|
else:
|
|
customer.pop('newborn_delivery_time', None)
|
|
cell_crud.upsert_party_customer(
|
|
db,
|
|
row.id,
|
|
ctx.tenant_no,
|
|
ctx.user_id,
|
|
commit=False,
|
|
create_dept_id=dept_id,
|
|
**customer,
|
|
)
|
|
if body.collection is not None:
|
|
cell_crud.upsert_collection_info(
|
|
db,
|
|
row.id,
|
|
ctx.tenant_no,
|
|
ctx.user_id,
|
|
commit=False,
|
|
create_dept_id=dept_id,
|
|
**collection,
|
|
)
|
|
if body.samples is not None:
|
|
cell_crud.save_samples(
|
|
db,
|
|
row.id,
|
|
ctx.tenant_no,
|
|
ctx.user_id,
|
|
samples,
|
|
create_dept_id=dept_id,
|
|
commit=False,
|
|
)
|
|
row = assert_cell_protocol_access(
|
|
db, row.id, ctx, user_info=user_info
|
|
)
|
|
db.commit()
|
|
db.refresh(row)
|
|
return _cell_detail(db, row, ctx.tenant_no)
|
|
|
|
row = assert_donation_access(db, protocol_id, ctx, user_info=user_info)
|
|
if (row.donation_status or '').strip() not in (
|
|
'',
|
|
'draft',
|
|
'dsx',
|
|
'dqr',
|
|
'sxz',
|
|
'ygq',
|
|
):
|
|
raise ValueError('当前状态的协议不可修改')
|
|
current_collection = donation_crud.get_collection(
|
|
db, row.id, ctx.tenant_no
|
|
)
|
|
current_party = donation_crud.get_party(db, row.id, ctx.tenant_no)
|
|
customer, collection, samples, attachments = _normalize_write(
|
|
db,
|
|
body=body,
|
|
ctx=ctx,
|
|
current_collection=current_collection,
|
|
)
|
|
attachments = _validate_mobile_attachments(
|
|
db,
|
|
attachments,
|
|
ctx=ctx,
|
|
creating=False,
|
|
existing={
|
|
'consent_form_urls': row.consent_form_urls,
|
|
'attachment_urls': row.attachment_urls,
|
|
'infectious_disease_report_urls': row.infectious_disease_report_urls,
|
|
},
|
|
business_id=row.id,
|
|
)
|
|
_assert_customer_link_policy(
|
|
customer, current_party=current_party, creating=False
|
|
)
|
|
if body.customer is not None and current_party is not None:
|
|
customer = {
|
|
**donation_crud._normalize_fields(
|
|
donation_crud._party_to_dict(current_party)
|
|
),
|
|
**customer,
|
|
}
|
|
if body.collection is not None and current_collection is not None:
|
|
collection = {
|
|
**donation_crud._normalize_fields(
|
|
donation_crud._collection_to_dict(current_collection)
|
|
),
|
|
**collection,
|
|
}
|
|
data = {
|
|
'donation_no': body.donation_no,
|
|
'donation_date': body.donation_date,
|
|
'collector_user_id': ctx.user_id
|
|
if ctx.scope_kind == 'specialist'
|
|
else body.collector_user_id,
|
|
'remark': body.remark,
|
|
'consent_form_urls': attachments.get('consent_form_urls'),
|
|
'attachment_urls': attachments.get('attachment_urls'),
|
|
'infectious_disease_report_urls': attachments.get(
|
|
'infectious_disease_report_urls'
|
|
),
|
|
}
|
|
if body.customer is not None:
|
|
data['party'] = customer
|
|
if body.collection is not None:
|
|
data['collection'] = collection
|
|
if body.samples is not None:
|
|
data['samples'] = samples
|
|
data = {key: value for key, value in data.items() if value is not None}
|
|
row = donation_crud.update_donation(
|
|
db,
|
|
row.id,
|
|
data=data,
|
|
tenant_no=ctx.tenant_no,
|
|
user_id=ctx.user_id,
|
|
create_dept_id=dept_id,
|
|
commit=False,
|
|
sync_customers=False,
|
|
)
|
|
row = assert_donation_access(db, row.id, ctx, user_info=user_info)
|
|
db.commit()
|
|
db.refresh(row)
|
|
return _donation_detail(db, row, ctx.tenant_no)
|
|
except Exception:
|
|
db.rollback()
|
|
raise
|