pilot_service.py 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496
  1. """中试服务管理 API(细胞产品类型/规格/价格体系/项目价格、渠道信息、体检机构)"""
  2. from typing import Any, List, Optional
  3. from fastapi import APIRouter, Depends, Query
  4. from pydantic import BaseModel, ConfigDict, Field
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.menu_guard import require_admin_or_super_admin
  8. from app.core.response import error_response, success_response
  9. from app.crud import pilot_service as ps_svc
  10. from app.models.models import SysUser
  11. router = APIRouter(prefix="/web/admin", tags=["中试服务管理"])
  12. def _get_tenant_no(db: Session, user: dict) -> str:
  13. user_id = user.get("user_id")
  14. if not user_id:
  15. raise PermissionError("用户身份信息缺失,无法确定租户")
  16. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  17. if not sys_user or not sys_user.tenant_no:
  18. raise PermissionError("用户租户信息缺失")
  19. return sys_user.tenant_no
  20. # ---------------------------------------------------------------------------
  21. # 1. 细胞产品类型
  22. # ---------------------------------------------------------------------------
  23. class CellProductTypeCreateBody(BaseModel):
  24. model_config = ConfigDict(populate_by_name=True)
  25. product_type_code: str = Field(..., alias="productTypeCode")
  26. product_type_name: str = Field(..., alias="productTypeName")
  27. order_num: Optional[int] = Field(0, alias="orderNum")
  28. remark: Optional[str] = None
  29. class CellProductTypeUpdateBody(BaseModel):
  30. model_config = ConfigDict(populate_by_name=True)
  31. product_type_code: Optional[str] = Field(None, alias="productTypeCode")
  32. product_type_name: Optional[str] = Field(None, alias="productTypeName")
  33. order_num: Optional[int] = Field(None, alias="orderNum")
  34. remark: Optional[str] = None
  35. @router.get("/cell-product-types")
  36. async def list_cell_product_types(
  37. page: int = Query(1, ge=1),
  38. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  39. keyword: Optional[str] = None,
  40. user: dict = Depends(require_admin_or_super_admin),
  41. db: Session = Depends(get_db),
  42. ):
  43. tenant_no = _get_tenant_no(db, user)
  44. items, total = ps_svc.list_cell_product_types(
  45. db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user
  46. )
  47. tp = ps_svc.total_pages(total, pageSize)
  48. return success_response(
  49. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  50. )
  51. @router.post("/cell-product-types")
  52. async def create_cell_product_type(
  53. body: CellProductTypeCreateBody,
  54. user: dict = Depends(require_admin_or_super_admin),
  55. db: Session = Depends(get_db),
  56. ):
  57. try:
  58. tenant_no = _get_tenant_no(db, user)
  59. obj = ps_svc.create_cell_product_type(
  60. db,
  61. product_type_code=body.product_type_code,
  62. product_type_name=body.product_type_name,
  63. tenant_no=tenant_no,
  64. order_num=body.order_num,
  65. remark=body.remark,
  66. user_id=user.get("user_id"),
  67. )
  68. return success_response(ps_svc._row_to_dict_cell_product_type(obj), message="创建成功")
  69. except ValueError as e:
  70. return error_response(400, str(e))
  71. @router.put("/cell-product-types/{row_id}")
  72. async def update_cell_product_type(
  73. row_id: str,
  74. body: CellProductTypeUpdateBody,
  75. user: dict = Depends(require_admin_or_super_admin),
  76. db: Session = Depends(get_db),
  77. ):
  78. try:
  79. tenant_no = _get_tenant_no(db, user)
  80. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  81. obj = ps_svc.update_cell_product_type(
  82. db,
  83. row_id,
  84. product_type_code=payload.get("product_type_code"),
  85. product_type_name=payload.get("product_type_name"),
  86. order_num=payload.get("order_num"),
  87. remark=payload.get("remark"),
  88. user_id=user.get("user_id"),
  89. tenant_no=tenant_no,
  90. )
  91. return success_response(ps_svc._row_to_dict_cell_product_type(obj), message="更新成功")
  92. except ValueError as e:
  93. return error_response(400, str(e))
  94. @router.delete("/cell-product-types/{row_id}")
  95. async def delete_cell_product_type(
  96. row_id: str,
  97. user: dict = Depends(require_admin_or_super_admin),
  98. db: Session = Depends(get_db),
  99. ):
  100. try:
  101. tenant_no = _get_tenant_no(db, user)
  102. ps_svc.soft_delete_cell_product_type(db, row_id, tenant_no=tenant_no)
  103. return success_response(None, message="删除成功")
  104. except ValueError as e:
  105. return error_response(400, str(e))
  106. # ---------------------------------------------------------------------------
  107. # 2. 细胞产品规格
  108. # ---------------------------------------------------------------------------
  109. class CellProductSpecCreateBody(BaseModel):
  110. model_config = ConfigDict(populate_by_name=True)
  111. spec_code: str = Field(..., alias="specCode")
  112. spec_name: str = Field(..., alias="specName")
  113. order_num: Optional[int] = Field(0, alias="orderNum")
  114. remark: Optional[str] = None
  115. class CellProductSpecUpdateBody(BaseModel):
  116. model_config = ConfigDict(populate_by_name=True)
  117. spec_code: Optional[str] = Field(None, alias="specCode")
  118. spec_name: Optional[str] = Field(None, alias="specName")
  119. order_num: Optional[int] = Field(None, alias="orderNum")
  120. remark: Optional[str] = None
  121. @router.get("/cell-product-specs")
  122. async def list_cell_product_specs(
  123. page: int = Query(1, ge=1),
  124. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  125. keyword: Optional[str] = None,
  126. user: dict = Depends(require_admin_or_super_admin),
  127. db: Session = Depends(get_db),
  128. ):
  129. tenant_no = _get_tenant_no(db, user)
  130. items, total = ps_svc.list_cell_product_specs(
  131. db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user
  132. )
  133. tp = ps_svc.total_pages(total, pageSize)
  134. return success_response(
  135. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  136. )
  137. @router.post("/cell-product-specs")
  138. async def create_cell_product_spec(
  139. body: CellProductSpecCreateBody,
  140. user: dict = Depends(require_admin_or_super_admin),
  141. db: Session = Depends(get_db),
  142. ):
  143. try:
  144. tenant_no = _get_tenant_no(db, user)
  145. obj = ps_svc.create_cell_product_spec(
  146. db,
  147. spec_code=body.spec_code,
  148. spec_name=body.spec_name,
  149. tenant_no=tenant_no,
  150. order_num=body.order_num,
  151. remark=body.remark,
  152. user_id=user.get("user_id"),
  153. )
  154. return success_response(ps_svc._row_to_dict_cell_product_spec(obj), message="创建成功")
  155. except ValueError as e:
  156. return error_response(400, str(e))
  157. @router.put("/cell-product-specs/{row_id}")
  158. async def update_cell_product_spec(
  159. row_id: str,
  160. body: CellProductSpecUpdateBody,
  161. user: dict = Depends(require_admin_or_super_admin),
  162. db: Session = Depends(get_db),
  163. ):
  164. try:
  165. tenant_no = _get_tenant_no(db, user)
  166. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  167. obj = ps_svc.update_cell_product_spec(
  168. db,
  169. row_id,
  170. spec_code=payload.get("spec_code"),
  171. spec_name=payload.get("spec_name"),
  172. order_num=payload.get("order_num"),
  173. remark=payload.get("remark"),
  174. user_id=user.get("user_id"),
  175. tenant_no=tenant_no,
  176. )
  177. return success_response(ps_svc._row_to_dict_cell_product_spec(obj), message="更新成功")
  178. except ValueError as e:
  179. return error_response(400, str(e))
  180. @router.delete("/cell-product-specs/{row_id}")
  181. async def delete_cell_product_spec(
  182. row_id: str,
  183. user: dict = Depends(require_admin_or_super_admin),
  184. db: Session = Depends(get_db),
  185. ):
  186. try:
  187. tenant_no = _get_tenant_no(db, user)
  188. ps_svc.soft_delete_cell_product_spec(db, row_id, tenant_no=tenant_no)
  189. return success_response(None, message="删除成功")
  190. except ValueError as e:
  191. return error_response(400, str(e))
  192. # ---------------------------------------------------------------------------
  193. # 2b. 细胞产品包装
  194. # ---------------------------------------------------------------------------
  195. class CellProductPackageCreateBody(BaseModel):
  196. model_config = ConfigDict(populate_by_name=True)
  197. package_code: str = Field(..., alias="packageCode")
  198. package_name: str = Field(..., alias="packageName")
  199. order_num: Optional[int] = Field(0, alias="orderNum")
  200. remark: Optional[str] = None
  201. class CellProductPackageUpdateBody(BaseModel):
  202. model_config = ConfigDict(populate_by_name=True)
  203. package_code: Optional[str] = Field(None, alias="packageCode")
  204. package_name: Optional[str] = Field(None, alias="packageName")
  205. order_num: Optional[int] = Field(None, alias="orderNum")
  206. remark: Optional[str] = None
  207. @router.get("/cell-product-packages")
  208. async def list_cell_product_packages(
  209. page: int = Query(1, ge=1),
  210. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  211. keyword: Optional[str] = None,
  212. user: dict = Depends(require_admin_or_super_admin),
  213. db: Session = Depends(get_db),
  214. ):
  215. tenant_no = _get_tenant_no(db, user)
  216. items, total = ps_svc.list_cell_product_packages(
  217. db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user
  218. )
  219. tp = ps_svc.total_pages(total, pageSize)
  220. return success_response(
  221. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  222. )
  223. @router.post("/cell-product-packages")
  224. async def create_cell_product_package(
  225. body: CellProductPackageCreateBody,
  226. user: dict = Depends(require_admin_or_super_admin),
  227. db: Session = Depends(get_db),
  228. ):
  229. try:
  230. tenant_no = _get_tenant_no(db, user)
  231. obj = ps_svc.create_cell_product_package(
  232. db,
  233. package_code=body.package_code,
  234. package_name=body.package_name,
  235. tenant_no=tenant_no,
  236. order_num=body.order_num,
  237. remark=body.remark,
  238. user_id=user.get("user_id"),
  239. )
  240. return success_response(ps_svc._row_to_dict_cell_product_package(obj), message="创建成功")
  241. except ValueError as e:
  242. return error_response(400, str(e))
  243. @router.put("/cell-product-packages/{row_id}")
  244. async def update_cell_product_package(
  245. row_id: str,
  246. body: CellProductPackageUpdateBody,
  247. user: dict = Depends(require_admin_or_super_admin),
  248. db: Session = Depends(get_db),
  249. ):
  250. try:
  251. tenant_no = _get_tenant_no(db, user)
  252. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  253. obj = ps_svc.update_cell_product_package(
  254. db,
  255. row_id,
  256. package_code=payload.get("package_code"),
  257. package_name=payload.get("package_name"),
  258. order_num=payload.get("order_num"),
  259. remark=payload.get("remark"),
  260. user_id=user.get("user_id"),
  261. tenant_no=tenant_no,
  262. )
  263. return success_response(ps_svc._row_to_dict_cell_product_package(obj), message="更新成功")
  264. except ValueError as e:
  265. return error_response(400, str(e))
  266. @router.delete("/cell-product-packages/{row_id}")
  267. async def delete_cell_product_package(
  268. row_id: str,
  269. user: dict = Depends(require_admin_or_super_admin),
  270. db: Session = Depends(get_db),
  271. ):
  272. try:
  273. tenant_no = _get_tenant_no(db, user)
  274. ps_svc.soft_delete_cell_product_package(db, row_id, tenant_no=tenant_no)
  275. return success_response(None, message="删除成功")
  276. except ValueError as e:
  277. return error_response(400, str(e))
  278. # ---------------------------------------------------------------------------
  279. # 3. 细胞产品价格
  280. # ---------------------------------------------------------------------------
  281. class CellProductPriceCreateBody(BaseModel):
  282. model_config = ConfigDict(populate_by_name=True)
  283. project_name: str = Field(..., alias="projectName")
  284. project_code: str = Field(..., alias="projectCode")
  285. cell_type_code: Optional[str] = Field(None, alias="cellTypeCode")
  286. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  287. spec_text: Optional[str] = Field(None, alias="specText")
  288. spec_id: Optional[str] = Field(None, alias="specId")
  289. cell_product_type_id: Optional[str] = Field(None, alias="cellProductTypeId")
  290. unit_price: Optional[float] = Field(None, alias="unitPrice")
  291. order_num: Optional[int] = Field(0, alias="orderNum")
  292. remark: Optional[str] = None
  293. class CellProductPriceUpdateBody(BaseModel):
  294. model_config = ConfigDict(populate_by_name=True)
  295. project_name: Optional[str] = Field(None, alias="projectName")
  296. project_code: Optional[str] = Field(None, alias="projectCode")
  297. cell_type_code: Optional[str] = Field(None, alias="cellTypeCode")
  298. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  299. spec_text: Optional[str] = Field(None, alias="specText")
  300. spec_id: Optional[str] = Field(None, alias="specId")
  301. cell_product_type_id: Optional[str] = Field(None, alias="cellProductTypeId")
  302. unit_price: Optional[float] = Field(None, alias="unitPrice")
  303. order_num: Optional[int] = Field(None, alias="orderNum")
  304. remark: Optional[str] = None
  305. @router.get("/cell-product-prices")
  306. async def list_cell_product_prices(
  307. page: int = Query(1, ge=1),
  308. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  309. keyword: Optional[str] = None,
  310. cell_product_type_id: Optional[str] = Query(None, alias="cellProductTypeId"),
  311. user: dict = Depends(require_admin_or_super_admin),
  312. db: Session = Depends(get_db),
  313. ):
  314. tenant_no = _get_tenant_no(db, user)
  315. items, total = ps_svc.list_cell_product_prices(
  316. db,
  317. page=page,
  318. page_size=pageSize,
  319. keyword=keyword,
  320. tenant_no=tenant_no,
  321. cell_product_type_id=cell_product_type_id,
  322. user_info=user,
  323. )
  324. tp = ps_svc.total_pages(total, pageSize)
  325. return success_response(
  326. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  327. )
  328. @router.post("/cell-product-prices")
  329. async def create_cell_product_price(
  330. body: CellProductPriceCreateBody,
  331. user: dict = Depends(require_admin_or_super_admin),
  332. db: Session = Depends(get_db),
  333. ):
  334. try:
  335. tenant_no = _get_tenant_no(db, user)
  336. obj = ps_svc.create_cell_product_price(
  337. db,
  338. project_name=body.project_name,
  339. project_code=body.project_code,
  340. cell_type_code=body.cell_type_code or "",
  341. cell_type_name=body.cell_type_name or "",
  342. spec_text=body.spec_text or "",
  343. spec_id=body.spec_id or "",
  344. cell_product_type_id=body.cell_product_type_id or "",
  345. unit_price=body.unit_price,
  346. tenant_no=tenant_no,
  347. order_num=body.order_num,
  348. remark=body.remark,
  349. user_id=user.get("user_id"),
  350. )
  351. return success_response(ps_svc._row_to_dict_cell_product_price(obj), message="创建成功")
  352. except ValueError as e:
  353. return error_response(400, str(e))
  354. @router.put("/cell-product-prices/{row_id}")
  355. async def update_cell_product_price(
  356. row_id: str,
  357. body: CellProductPriceUpdateBody,
  358. user: dict = Depends(require_admin_or_super_admin),
  359. db: Session = Depends(get_db),
  360. ):
  361. try:
  362. tenant_no = _get_tenant_no(db, user)
  363. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  364. obj = ps_svc.update_cell_product_price(
  365. db,
  366. row_id,
  367. project_name=payload.get("project_name"),
  368. project_code=payload.get("project_code"),
  369. cell_type_code=payload.get("cell_type_code"),
  370. cell_type_name=payload.get("cell_type_name"),
  371. spec_text=payload.get("spec_text"),
  372. spec_id=payload.get("spec_id"),
  373. cell_product_type_id=payload.get("cell_product_type_id"),
  374. unit_price=payload.get("unit_price"),
  375. order_num=payload.get("order_num"),
  376. remark=payload.get("remark"),
  377. user_id=user.get("user_id"),
  378. tenant_no=tenant_no,
  379. )
  380. return success_response(ps_svc._row_to_dict_cell_product_price(obj), message="更新成功")
  381. except ValueError as e:
  382. return error_response(400, str(e))
  383. @router.delete("/cell-product-prices/{row_id}")
  384. async def delete_cell_product_price(
  385. row_id: str,
  386. user: dict = Depends(require_admin_or_super_admin),
  387. db: Session = Depends(get_db),
  388. ):
  389. try:
  390. tenant_no = _get_tenant_no(db, user)
  391. ps_svc.soft_delete_cell_product_price(db, row_id, tenant_no=tenant_no)
  392. return success_response(None, message="删除成功")
  393. except ValueError as e:
  394. return error_response(400, str(e))
  395. # ---------------------------------------------------------------------------
  396. # 4. 项目价格体系(主表 + 明细子表)
  397. # ---------------------------------------------------------------------------
  398. class ProjectSystemCreateBody(BaseModel):
  399. model_config = ConfigDict(populate_by_name=True)
  400. system_name: str = Field(..., alias="systemName")
  401. system_code: str = Field(..., alias="systemCode")
  402. status: Optional[str] = Field("1", alias="status")
  403. order_num: Optional[int] = Field(0, alias="orderNum")
  404. remark: Optional[str] = None
  405. class ProjectSystemUpdateBody(BaseModel):
  406. model_config = ConfigDict(populate_by_name=True)
  407. system_name: Optional[str] = Field(None, alias="systemName")
  408. system_code: Optional[str] = Field(None, alias="systemCode")
  409. status: Optional[str] = Field(None, alias="status")
  410. order_num: Optional[int] = Field(None, alias="orderNum")
  411. remark: Optional[str] = None
  412. @router.get("/project-price-systems")
  413. async def list_project_systems(
  414. page: int = Query(1, ge=1),
  415. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  416. keyword: Optional[str] = None,
  417. user: dict = Depends(require_admin_or_super_admin),
  418. db: Session = Depends(get_db),
  419. ):
  420. tenant_no = _get_tenant_no(db, user)
  421. items, total = ps_svc.list_project_systems(
  422. db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user
  423. )
  424. tp = ps_svc.total_pages(total, pageSize)
  425. return success_response(
  426. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  427. )
  428. @router.post("/project-price-systems")
  429. async def create_project_system(
  430. body: ProjectSystemCreateBody,
  431. user: dict = Depends(require_admin_or_super_admin),
  432. db: Session = Depends(get_db),
  433. ):
  434. try:
  435. tenant_no = _get_tenant_no(db, user)
  436. obj = ps_svc.create_project_system(
  437. db,
  438. system_name=body.system_name,
  439. system_code=body.system_code,
  440. status=body.status,
  441. tenant_no=tenant_no,
  442. order_num=body.order_num,
  443. remark=body.remark,
  444. user_id=user.get("user_id"),
  445. )
  446. return success_response(ps_svc._row_to_dict_project_system(obj), message="创建成功")
  447. except ValueError as e:
  448. return error_response(400, str(e))
  449. @router.put("/project-price-systems/{row_id}")
  450. async def update_project_system(
  451. row_id: str,
  452. body: ProjectSystemUpdateBody,
  453. user: dict = Depends(require_admin_or_super_admin),
  454. db: Session = Depends(get_db),
  455. ):
  456. try:
  457. tenant_no = _get_tenant_no(db, user)
  458. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  459. obj = ps_svc.update_project_system(
  460. db,
  461. row_id,
  462. system_name=payload.get("system_name"),
  463. system_code=payload.get("system_code"),
  464. status=payload.get("status"),
  465. order_num=payload.get("order_num"),
  466. remark=payload.get("remark"),
  467. user_id=user.get("user_id"),
  468. tenant_no=tenant_no,
  469. )
  470. return success_response(ps_svc._row_to_dict_project_system(obj), message="更新成功")
  471. except ValueError as e:
  472. return error_response(400, str(e))
  473. @router.delete("/project-price-systems/{row_id}")
  474. async def delete_project_system(
  475. row_id: str,
  476. user: dict = Depends(require_admin_or_super_admin),
  477. db: Session = Depends(get_db),
  478. ):
  479. try:
  480. tenant_no = _get_tenant_no(db, user)
  481. ps_svc.soft_delete_project_system(db, row_id, tenant_no=tenant_no)
  482. return success_response(None, message="删除成功")
  483. except ValueError as e:
  484. return error_response(400, str(e))
  485. # --- 项目价格体系明细 ---
  486. class ProjectSystemItemCreateBody(BaseModel):
  487. model_config = ConfigDict(populate_by_name=True)
  488. source_price_id: Optional[str] = Field(None, alias="sourcePriceId")
  489. project_name: str = Field(..., alias="projectName")
  490. project_code: str = Field(..., alias="projectCode")
  491. cell_type_code: Optional[str] = Field(None, alias="cellTypeCode")
  492. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  493. spec_text: Optional[str] = Field(None, alias="specText")
  494. spec_id: Optional[str] = Field(None, alias="specId")
  495. cell_product_type_id: Optional[str] = Field(None, alias="cellProductTypeId")
  496. unit_price: Optional[float] = Field(None, alias="unitPrice")
  497. order_num: Optional[int] = Field(0, alias="orderNum")
  498. remark: Optional[str] = None
  499. class ProjectSystemItemUpdateBody(BaseModel):
  500. model_config = ConfigDict(populate_by_name=True)
  501. source_price_id: Optional[str] = Field(None, alias="sourcePriceId")
  502. project_name: Optional[str] = Field(None, alias="projectName")
  503. project_code: Optional[str] = Field(None, alias="projectCode")
  504. cell_type_code: Optional[str] = Field(None, alias="cellTypeCode")
  505. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  506. spec_text: Optional[str] = Field(None, alias="specText")
  507. spec_id: Optional[str] = Field(None, alias="specId")
  508. cell_product_type_id: Optional[str] = Field(None, alias="cellProductTypeId")
  509. unit_price: Optional[float] = Field(None, alias="unitPrice")
  510. order_num: Optional[int] = Field(None, alias="orderNum")
  511. remark: Optional[str] = None
  512. @router.get("/project-price-systems/{system_id}/items")
  513. async def list_project_system_items(
  514. system_id: str,
  515. user: dict = Depends(require_admin_or_super_admin),
  516. db: Session = Depends(get_db),
  517. ):
  518. try:
  519. tenant_no = _get_tenant_no(db, user)
  520. items = ps_svc.list_project_system_items(db, system_id, tenant_no=tenant_no, user_info=user)
  521. return success_response(items)
  522. except ValueError as e:
  523. return error_response(400, str(e))
  524. @router.post("/project-price-systems/{system_id}/items")
  525. async def create_project_system_item(
  526. system_id: str,
  527. body: ProjectSystemItemCreateBody,
  528. user: dict = Depends(require_admin_or_super_admin),
  529. db: Session = Depends(get_db),
  530. ):
  531. try:
  532. tenant_no = _get_tenant_no(db, user)
  533. obj = ps_svc.create_project_system_item(
  534. db,
  535. project_system_id=system_id,
  536. source_price_id=body.source_price_id or "",
  537. project_name=body.project_name,
  538. project_code=body.project_code,
  539. cell_type_code=body.cell_type_code or "",
  540. cell_type_name=body.cell_type_name or "",
  541. spec_text=body.spec_text or "",
  542. spec_id=body.spec_id or "",
  543. cell_product_type_id=body.cell_product_type_id or "",
  544. unit_price=body.unit_price,
  545. tenant_no=tenant_no,
  546. order_num=body.order_num,
  547. remark=body.remark,
  548. user_id=user.get("user_id"),
  549. )
  550. return success_response(ps_svc._row_to_dict_project_system_item(obj), message="创建成功")
  551. except ValueError as e:
  552. return error_response(400, str(e))
  553. @router.put("/project-system-items/{item_id}")
  554. async def update_project_system_item(
  555. item_id: str,
  556. body: ProjectSystemItemUpdateBody,
  557. user: dict = Depends(require_admin_or_super_admin),
  558. db: Session = Depends(get_db),
  559. ):
  560. try:
  561. tenant_no = _get_tenant_no(db, user)
  562. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  563. obj = ps_svc.update_project_system_item(
  564. db,
  565. item_id,
  566. source_price_id=payload.get("source_price_id"),
  567. project_name=payload.get("project_name"),
  568. project_code=payload.get("project_code"),
  569. cell_type_code=payload.get("cell_type_code"),
  570. cell_type_name=payload.get("cell_type_name"),
  571. spec_text=payload.get("spec_text"),
  572. spec_id=payload.get("spec_id"),
  573. cell_product_type_id=payload.get("cell_product_type_id"),
  574. unit_price=payload.get("unit_price"),
  575. order_num=payload.get("order_num"),
  576. remark=payload.get("remark"),
  577. user_id=user.get("user_id"),
  578. tenant_no=tenant_no,
  579. )
  580. return success_response(ps_svc._row_to_dict_project_system_item(obj), message="更新成功")
  581. except ValueError as e:
  582. return error_response(400, str(e))
  583. @router.delete("/project-system-items/{item_id}")
  584. async def delete_project_system_item(
  585. item_id: str,
  586. user: dict = Depends(require_admin_or_super_admin),
  587. db: Session = Depends(get_db),
  588. ):
  589. try:
  590. tenant_no = _get_tenant_no(db, user)
  591. ps_svc.soft_delete_project_system_item(db, item_id, tenant_no=tenant_no)
  592. return success_response(None, message="删除成功")
  593. except ValueError as e:
  594. return error_response(400, str(e))
  595. # ---------------------------------------------------------------------------
  596. # 5. 渠道信息(主表 + 开票/邮寄地址/协议/协议产品价格子表)
  597. # ---------------------------------------------------------------------------
  598. class ChannelCreateBody(BaseModel):
  599. model_config = ConfigDict(populate_by_name=True)
  600. channel_name: str = Field(..., alias="channelName")
  601. credit_code: Optional[str] = Field(None, alias="creditCode")
  602. channel_type: Optional[str] = Field(None, alias="channelType")
  603. contact_name: Optional[str] = Field(None, alias="contactName")
  604. contact_phone: Optional[str] = Field(None, alias="contactPhone")
  605. contact_email: Optional[str] = Field(None, alias="contactEmail")
  606. protocol_attachments: Optional[str] = Field(None, alias="protocolAttachments")
  607. order_num: Optional[int] = Field(0, alias="orderNum")
  608. remark: Optional[str] = None
  609. class ChannelUpdateBody(BaseModel):
  610. model_config = ConfigDict(populate_by_name=True)
  611. channel_name: Optional[str] = Field(None, alias="channelName")
  612. credit_code: Optional[str] = Field(None, alias="creditCode")
  613. channel_type: Optional[str] = Field(None, alias="channelType")
  614. contact_name: Optional[str] = Field(None, alias="contactName")
  615. contact_phone: Optional[str] = Field(None, alias="contactPhone")
  616. contact_email: Optional[str] = Field(None, alias="contactEmail")
  617. protocol_attachments: Optional[str] = Field(None, alias="protocolAttachments")
  618. order_num: Optional[int] = Field(None, alias="orderNum")
  619. remark: Optional[str] = None
  620. @router.get("/channels")
  621. async def list_channels(
  622. page: int = Query(1, ge=1),
  623. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  624. keyword: Optional[str] = None,
  625. user: dict = Depends(require_admin_or_super_admin),
  626. db: Session = Depends(get_db),
  627. ):
  628. tenant_no = _get_tenant_no(db, user)
  629. items, total = ps_svc.list_channels(
  630. db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user
  631. )
  632. tp = ps_svc.total_pages(total, pageSize)
  633. return success_response(
  634. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  635. )
  636. @router.post("/channels")
  637. async def create_channel(
  638. body: ChannelCreateBody,
  639. user: dict = Depends(require_admin_or_super_admin),
  640. db: Session = Depends(get_db),
  641. ):
  642. try:
  643. tenant_no = _get_tenant_no(db, user)
  644. obj = ps_svc.create_channel(
  645. db,
  646. channel_name=body.channel_name,
  647. credit_code=body.credit_code,
  648. channel_type=body.channel_type,
  649. contact_name=body.contact_name,
  650. contact_phone=body.contact_phone,
  651. contact_email=body.contact_email,
  652. protocol_attachments=body.protocol_attachments,
  653. tenant_no=tenant_no,
  654. order_num=body.order_num,
  655. remark=body.remark,
  656. user_id=user.get("user_id"),
  657. )
  658. return success_response(
  659. ps_svc._row_to_dict_channel(obj, db, tenant_no),
  660. message="创建成功",
  661. )
  662. except ValueError as e:
  663. return error_response(400, str(e))
  664. @router.put("/channels/{row_id}")
  665. async def update_channel(
  666. row_id: str,
  667. body: ChannelUpdateBody,
  668. user: dict = Depends(require_admin_or_super_admin),
  669. db: Session = Depends(get_db),
  670. ):
  671. try:
  672. tenant_no = _get_tenant_no(db, user)
  673. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  674. obj = ps_svc.update_channel(
  675. db,
  676. row_id,
  677. channel_name=payload.get("channel_name"),
  678. credit_code=payload.get("credit_code"),
  679. channel_type=payload.get("channel_type"),
  680. contact_name=payload.get("contact_name"),
  681. contact_phone=payload.get("contact_phone"),
  682. contact_email=payload.get("contact_email"),
  683. protocol_attachments=payload.get("protocol_attachments"),
  684. order_num=payload.get("order_num"),
  685. remark=payload.get("remark"),
  686. user_id=user.get("user_id"),
  687. tenant_no=tenant_no,
  688. )
  689. return success_response(
  690. ps_svc._row_to_dict_channel(obj, db, tenant_no),
  691. message="更新成功",
  692. )
  693. except ValueError as e:
  694. return error_response(400, str(e))
  695. @router.delete("/channels/{row_id}")
  696. async def delete_channel(
  697. row_id: str,
  698. user: dict = Depends(require_admin_or_super_admin),
  699. db: Session = Depends(get_db),
  700. ):
  701. try:
  702. tenant_no = _get_tenant_no(db, user)
  703. ps_svc.soft_delete_channel(db, row_id, tenant_no=tenant_no)
  704. return success_response(None, message="删除成功")
  705. except ValueError as e:
  706. return error_response(400, str(e))
  707. # --- 渠道开票信息 ---
  708. class ChannelInvoiceCreateBody(BaseModel):
  709. model_config = ConfigDict(populate_by_name=True)
  710. company_name: str = Field(..., alias="companyName")
  711. tax_no: Optional[str] = Field(None, alias="taxNo")
  712. company_address: Optional[str] = Field(None, alias="companyAddress")
  713. company_phone: Optional[str] = Field(None, alias="companyPhone")
  714. bank_name: Optional[str] = Field(None, alias="bankName")
  715. bank_account: Optional[str] = Field(None, alias="bankAccount")
  716. order_num: Optional[int] = Field(0, alias="orderNum")
  717. remark: Optional[str] = None
  718. class ChannelInvoiceUpdateBody(BaseModel):
  719. model_config = ConfigDict(populate_by_name=True)
  720. company_name: Optional[str] = Field(None, alias="companyName")
  721. tax_no: Optional[str] = Field(None, alias="taxNo")
  722. company_address: Optional[str] = Field(None, alias="companyAddress")
  723. company_phone: Optional[str] = Field(None, alias="companyPhone")
  724. bank_name: Optional[str] = Field(None, alias="bankName")
  725. bank_account: Optional[str] = Field(None, alias="bankAccount")
  726. order_num: Optional[int] = Field(None, alias="orderNum")
  727. remark: Optional[str] = None
  728. @router.get("/channels/{channel_id}/invoices")
  729. async def list_channel_invoices(
  730. channel_id: str,
  731. user: dict = Depends(require_admin_or_super_admin),
  732. db: Session = Depends(get_db),
  733. ):
  734. try:
  735. tenant_no = _get_tenant_no(db, user)
  736. items = ps_svc.list_channel_invoices(db, channel_id, tenant_no=tenant_no, user_info=user)
  737. return success_response(items)
  738. except ValueError as e:
  739. return error_response(400, str(e))
  740. @router.post("/channels/{channel_id}/invoices")
  741. async def create_channel_invoice(
  742. channel_id: str,
  743. body: ChannelInvoiceCreateBody,
  744. user: dict = Depends(require_admin_or_super_admin),
  745. db: Session = Depends(get_db),
  746. ):
  747. try:
  748. tenant_no = _get_tenant_no(db, user)
  749. obj = ps_svc.create_channel_invoice(
  750. db,
  751. channel_id=channel_id,
  752. company_name=body.company_name,
  753. tax_no=body.tax_no,
  754. company_address=body.company_address,
  755. company_phone=body.company_phone,
  756. bank_name=body.bank_name,
  757. bank_account=body.bank_account,
  758. tenant_no=tenant_no,
  759. order_num=body.order_num,
  760. remark=body.remark,
  761. user_id=user.get("user_id"),
  762. )
  763. return success_response(ps_svc._row_to_dict_channel_invoice(obj), message="创建成功")
  764. except ValueError as e:
  765. return error_response(400, str(e))
  766. @router.put("/channel-invoices/{invoice_id}")
  767. async def update_channel_invoice(
  768. invoice_id: str,
  769. body: ChannelInvoiceUpdateBody,
  770. user: dict = Depends(require_admin_or_super_admin),
  771. db: Session = Depends(get_db),
  772. ):
  773. try:
  774. tenant_no = _get_tenant_no(db, user)
  775. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  776. obj = ps_svc.update_channel_invoice(
  777. db,
  778. invoice_id,
  779. company_name=payload.get("company_name"),
  780. tax_no=payload.get("tax_no"),
  781. company_address=payload.get("company_address"),
  782. company_phone=payload.get("company_phone"),
  783. bank_name=payload.get("bank_name"),
  784. bank_account=payload.get("bank_account"),
  785. order_num=payload.get("order_num"),
  786. remark=payload.get("remark"),
  787. user_id=user.get("user_id"),
  788. tenant_no=tenant_no,
  789. )
  790. return success_response(ps_svc._row_to_dict_channel_invoice(obj), message="更新成功")
  791. except ValueError as e:
  792. return error_response(400, str(e))
  793. @router.delete("/channel-invoices/{invoice_id}")
  794. async def delete_channel_invoice(
  795. invoice_id: str,
  796. user: dict = Depends(require_admin_or_super_admin),
  797. db: Session = Depends(get_db),
  798. ):
  799. try:
  800. tenant_no = _get_tenant_no(db, user)
  801. ps_svc.soft_delete_channel_invoice(db, invoice_id, tenant_no=tenant_no)
  802. return success_response(None, message="删除成功")
  803. except ValueError as e:
  804. return error_response(400, str(e))
  805. # --- 渠道邮寄地址 ---
  806. class ChannelMailAddressCreateBody(BaseModel):
  807. model_config = ConfigDict(populate_by_name=True)
  808. mail_address: str = Field(..., alias="mailAddress")
  809. mail_contact: Optional[str] = Field(None, alias="mailContact")
  810. mail_phone: Optional[str] = Field(None, alias="mailPhone")
  811. order_num: Optional[int] = Field(0, alias="orderNum")
  812. remark: Optional[str] = None
  813. class ChannelMailAddressUpdateBody(BaseModel):
  814. model_config = ConfigDict(populate_by_name=True)
  815. mail_address: Optional[str] = Field(None, alias="mailAddress")
  816. mail_contact: Optional[str] = Field(None, alias="mailContact")
  817. mail_phone: Optional[str] = Field(None, alias="mailPhone")
  818. order_num: Optional[int] = Field(None, alias="orderNum")
  819. remark: Optional[str] = None
  820. @router.get("/channels/{channel_id}/mail-addresses")
  821. async def list_channel_mail_addresses(
  822. channel_id: str,
  823. user: dict = Depends(require_admin_or_super_admin),
  824. db: Session = Depends(get_db),
  825. ):
  826. try:
  827. tenant_no = _get_tenant_no(db, user)
  828. items = ps_svc.list_channel_mail_addresses(db, channel_id, tenant_no=tenant_no, user_info=user)
  829. return success_response(items)
  830. except ValueError as e:
  831. return error_response(400, str(e))
  832. @router.post("/channels/{channel_id}/mail-addresses")
  833. async def create_channel_mail_address(
  834. channel_id: str,
  835. body: ChannelMailAddressCreateBody,
  836. user: dict = Depends(require_admin_or_super_admin),
  837. db: Session = Depends(get_db),
  838. ):
  839. try:
  840. tenant_no = _get_tenant_no(db, user)
  841. obj = ps_svc.create_channel_mail_address(
  842. db,
  843. channel_id=channel_id,
  844. mail_address=body.mail_address,
  845. mail_contact=body.mail_contact,
  846. mail_phone=body.mail_phone,
  847. tenant_no=tenant_no,
  848. order_num=body.order_num,
  849. remark=body.remark,
  850. user_id=user.get("user_id"),
  851. )
  852. return success_response(ps_svc._row_to_dict_channel_mail_address(obj), message="创建成功")
  853. except ValueError as e:
  854. return error_response(400, str(e))
  855. @router.put("/channel-mail-addresses/{address_id}")
  856. async def update_channel_mail_address(
  857. address_id: str,
  858. body: ChannelMailAddressUpdateBody,
  859. user: dict = Depends(require_admin_or_super_admin),
  860. db: Session = Depends(get_db),
  861. ):
  862. try:
  863. tenant_no = _get_tenant_no(db, user)
  864. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  865. obj = ps_svc.update_channel_mail_address(
  866. db,
  867. address_id,
  868. mail_address=payload.get("mail_address"),
  869. mail_contact=payload.get("mail_contact"),
  870. mail_phone=payload.get("mail_phone"),
  871. order_num=payload.get("order_num"),
  872. remark=payload.get("remark"),
  873. user_id=user.get("user_id"),
  874. tenant_no=tenant_no,
  875. )
  876. return success_response(ps_svc._row_to_dict_channel_mail_address(obj), message="更新成功")
  877. except ValueError as e:
  878. return error_response(400, str(e))
  879. @router.delete("/channel-mail-addresses/{address_id}")
  880. async def delete_channel_mail_address(
  881. address_id: str,
  882. user: dict = Depends(require_admin_or_super_admin),
  883. db: Session = Depends(get_db),
  884. ):
  885. try:
  886. tenant_no = _get_tenant_no(db, user)
  887. ps_svc.soft_delete_channel_mail_address(db, address_id, tenant_no=tenant_no)
  888. return success_response(None, message="删除成功")
  889. except ValueError as e:
  890. return error_response(400, str(e))
  891. # --- 渠道协议 ---
  892. class ChannelAgreementCreateBody(BaseModel):
  893. model_config = ConfigDict(populate_by_name=True)
  894. agreement_type: str = Field(..., alias="agreementType")
  895. business_type: Optional[str] = Field(None, alias="businessType")
  896. settle_mode: Optional[str] = Field(None, alias="settleMode")
  897. credit_limit_amount: Optional[float] = Field(None, alias="creditLimitAmount")
  898. effective_date: Optional[str] = Field(None, alias="effectiveDate")
  899. expire_date: Optional[str] = Field(None, alias="expireDate")
  900. order_num: Optional[int] = Field(0, alias="orderNum")
  901. remark: Optional[str] = None
  902. class ChannelAgreementUpdateBody(BaseModel):
  903. model_config = ConfigDict(populate_by_name=True)
  904. agreement_type: Optional[str] = Field(None, alias="agreementType")
  905. business_type: Optional[str] = Field(None, alias="businessType")
  906. settle_mode: Optional[str] = Field(None, alias="settleMode")
  907. credit_limit_amount: Optional[float] = Field(None, alias="creditLimitAmount")
  908. effective_date: Optional[str] = Field(None, alias="effectiveDate")
  909. expire_date: Optional[str] = Field(None, alias="expireDate")
  910. order_num: Optional[int] = Field(None, alias="orderNum")
  911. remark: Optional[str] = None
  912. @router.get("/channels/{channel_id}/agreements")
  913. async def list_channel_agreements(
  914. channel_id: str,
  915. user: dict = Depends(require_admin_or_super_admin),
  916. db: Session = Depends(get_db),
  917. ):
  918. try:
  919. tenant_no = _get_tenant_no(db, user)
  920. items = ps_svc.list_channel_agreements(db, channel_id, tenant_no=tenant_no, user_info=user)
  921. return success_response(items)
  922. except ValueError as e:
  923. return error_response(400, str(e))
  924. @router.post("/channels/{channel_id}/agreements")
  925. async def create_channel_agreement(
  926. channel_id: str,
  927. body: ChannelAgreementCreateBody,
  928. user: dict = Depends(require_admin_or_super_admin),
  929. db: Session = Depends(get_db),
  930. ):
  931. try:
  932. tenant_no = _get_tenant_no(db, user)
  933. obj = ps_svc.create_channel_agreement(
  934. db,
  935. channel_id=channel_id,
  936. agreement_type=body.agreement_type,
  937. business_type=body.business_type,
  938. settle_mode=body.settle_mode,
  939. credit_limit_amount=body.credit_limit_amount,
  940. effective_date=body.effective_date,
  941. expire_date=body.expire_date,
  942. tenant_no=tenant_no,
  943. order_num=body.order_num,
  944. remark=body.remark,
  945. user_id=user.get("user_id"),
  946. )
  947. return success_response(ps_svc._row_to_dict_channel_agreement(obj), message="创建成功")
  948. except ValueError as e:
  949. return error_response(400, str(e))
  950. @router.put("/channel-agreements/{agreement_id}")
  951. async def update_channel_agreement(
  952. agreement_id: str,
  953. body: ChannelAgreementUpdateBody,
  954. user: dict = Depends(require_admin_or_super_admin),
  955. db: Session = Depends(get_db),
  956. ):
  957. try:
  958. tenant_no = _get_tenant_no(db, user)
  959. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  960. obj = ps_svc.update_channel_agreement(
  961. db,
  962. agreement_id,
  963. agreement_type=payload.get("agreement_type"),
  964. business_type=payload.get("business_type"),
  965. settle_mode=payload.get("settle_mode"),
  966. credit_limit_amount=payload.get("credit_limit_amount"),
  967. effective_date=payload.get("effective_date"),
  968. expire_date=payload.get("expire_date"),
  969. order_num=payload.get("order_num"),
  970. remark=payload.get("remark"),
  971. user_id=user.get("user_id"),
  972. tenant_no=tenant_no,
  973. )
  974. return success_response(ps_svc._row_to_dict_channel_agreement(obj), message="更新成功")
  975. except ValueError as e:
  976. return error_response(400, str(e))
  977. @router.delete("/channel-agreements/{agreement_id}")
  978. async def delete_channel_agreement(
  979. agreement_id: str,
  980. user: dict = Depends(require_admin_or_super_admin),
  981. db: Session = Depends(get_db),
  982. ):
  983. try:
  984. tenant_no = _get_tenant_no(db, user)
  985. ps_svc.soft_delete_channel_agreement(db, agreement_id, tenant_no=tenant_no)
  986. return success_response(None, message="删除成功")
  987. except ValueError as e:
  988. return error_response(400, str(e))
  989. @router.put("/channel-agreements/{agreement_id}/activate")
  990. async def activate_channel_agreement(
  991. agreement_id: str,
  992. user: dict = Depends(require_admin_or_super_admin),
  993. db: Session = Depends(get_db),
  994. ):
  995. try:
  996. tenant_no = _get_tenant_no(db, user)
  997. obj = ps_svc.activate_channel_agreement(
  998. db,
  999. agreement_id,
  1000. tenant_no=tenant_no,
  1001. user_id=user.get("user_id"),
  1002. )
  1003. return success_response(ps_svc._row_to_dict_channel_agreement(obj), message="生效成功")
  1004. except ValueError as e:
  1005. return error_response(400, str(e))
  1006. @router.put("/channel-agreements/{agreement_id}/void")
  1007. async def void_channel_agreement(
  1008. agreement_id: str,
  1009. user: dict = Depends(require_admin_or_super_admin),
  1010. db: Session = Depends(get_db),
  1011. ):
  1012. try:
  1013. tenant_no = _get_tenant_no(db, user)
  1014. obj = ps_svc.void_channel_agreement(
  1015. db,
  1016. agreement_id,
  1017. tenant_no=tenant_no,
  1018. user_id=user.get("user_id"),
  1019. )
  1020. return success_response(ps_svc._row_to_dict_channel_agreement(obj), message="作废成功")
  1021. except ValueError as e:
  1022. return error_response(400, str(e))
  1023. # --- 渠道协议产品价格 ---
  1024. class ChannelAgreementPriceCreateBody(BaseModel):
  1025. model_config = ConfigDict(populate_by_name=True)
  1026. project_name: Optional[str] = Field(None, alias="projectName")
  1027. project_code: Optional[str] = Field(None, alias="projectCode")
  1028. cell_type_code: Optional[str] = Field(None, alias="cellTypeCode")
  1029. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  1030. spec_text: Optional[str] = Field(None, alias="specText")
  1031. original_price: Optional[float] = Field(None, alias="originalPrice")
  1032. agreement_price: Optional[float] = Field(None, alias="agreementPrice")
  1033. order_num: Optional[int] = Field(0, alias="orderNum")
  1034. remark: Optional[str] = None
  1035. class ChannelAgreementPriceUpdateBody(BaseModel):
  1036. model_config = ConfigDict(populate_by_name=True)
  1037. project_name: Optional[str] = Field(None, alias="projectName")
  1038. project_code: Optional[str] = Field(None, alias="projectCode")
  1039. cell_type_code: Optional[str] = Field(None, alias="cellTypeCode")
  1040. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  1041. spec_text: Optional[str] = Field(None, alias="specText")
  1042. original_price: Optional[float] = Field(None, alias="originalPrice")
  1043. agreement_price: Optional[float] = Field(None, alias="agreementPrice")
  1044. order_num: Optional[int] = Field(None, alias="orderNum")
  1045. remark: Optional[str] = None
  1046. @router.get("/channel-agreements/{agreement_id}/product-prices")
  1047. async def list_channel_agreement_prices(
  1048. agreement_id: str,
  1049. user: dict = Depends(require_admin_or_super_admin),
  1050. db: Session = Depends(get_db),
  1051. ):
  1052. try:
  1053. tenant_no = _get_tenant_no(db, user)
  1054. items = ps_svc.list_channel_agreement_prices(db, agreement_id, tenant_no=tenant_no, user_info=user)
  1055. return success_response(items)
  1056. except ValueError as e:
  1057. return error_response(400, str(e))
  1058. @router.post("/channel-agreements/{agreement_id}/product-prices")
  1059. async def create_channel_agreement_price(
  1060. agreement_id: str,
  1061. body: ChannelAgreementPriceCreateBody,
  1062. user: dict = Depends(require_admin_or_super_admin),
  1063. db: Session = Depends(get_db),
  1064. ):
  1065. try:
  1066. tenant_no = _get_tenant_no(db, user)
  1067. obj = ps_svc.create_channel_agreement_price(
  1068. db,
  1069. channel_agreement_id=agreement_id,
  1070. project_name=body.project_name,
  1071. project_code=body.project_code,
  1072. cell_type_code=body.cell_type_code,
  1073. cell_type_name=body.cell_type_name,
  1074. spec_text=body.spec_text,
  1075. original_price=body.original_price,
  1076. agreement_price=body.agreement_price,
  1077. tenant_no=tenant_no,
  1078. order_num=body.order_num,
  1079. remark=body.remark,
  1080. user_id=user.get("user_id"),
  1081. )
  1082. return success_response(ps_svc._row_to_dict_channel_agreement_price(obj), message="创建成功")
  1083. except ValueError as e:
  1084. return error_response(400, str(e))
  1085. @router.put("/channel-agreement-product-prices/{price_id}")
  1086. async def update_channel_agreement_price(
  1087. price_id: str,
  1088. body: ChannelAgreementPriceUpdateBody,
  1089. user: dict = Depends(require_admin_or_super_admin),
  1090. db: Session = Depends(get_db),
  1091. ):
  1092. try:
  1093. tenant_no = _get_tenant_no(db, user)
  1094. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  1095. obj = ps_svc.update_channel_agreement_price(
  1096. db,
  1097. price_id,
  1098. project_name=payload.get("project_name"),
  1099. project_code=payload.get("project_code"),
  1100. cell_type_code=payload.get("cell_type_code"),
  1101. cell_type_name=payload.get("cell_type_name"),
  1102. spec_text=payload.get("spec_text"),
  1103. original_price=payload.get("original_price"),
  1104. agreement_price=payload.get("agreement_price"),
  1105. order_num=payload.get("order_num"),
  1106. remark=payload.get("remark"),
  1107. user_id=user.get("user_id"),
  1108. tenant_no=tenant_no,
  1109. )
  1110. return success_response(ps_svc._row_to_dict_channel_agreement_price(obj), message="更新成功")
  1111. except ValueError as e:
  1112. return error_response(400, str(e))
  1113. @router.delete("/channel-agreement-product-prices/{price_id}")
  1114. async def delete_channel_agreement_price(
  1115. price_id: str,
  1116. user: dict = Depends(require_admin_or_super_admin),
  1117. db: Session = Depends(get_db),
  1118. ):
  1119. try:
  1120. tenant_no = _get_tenant_no(db, user)
  1121. ps_svc.soft_delete_channel_agreement_price(db, price_id, tenant_no=tenant_no)
  1122. return success_response(None, message="删除成功")
  1123. except ValueError as e:
  1124. return error_response(400, str(e))
  1125. # ---------------------------------------------------------------------------
  1126. # 6. 体检机构(主表 + 开票信息/项目及价格子表)
  1127. # ---------------------------------------------------------------------------
  1128. class CheckupInstitutionCreateBody(BaseModel):
  1129. model_config = ConfigDict(populate_by_name=True)
  1130. institution_name: str = Field(..., alias="institutionName")
  1131. institution_address: Optional[str] = Field(None, alias="institutionAddress")
  1132. contact_name: Optional[str] = Field(None, alias="contactName")
  1133. contact_phone: Optional[str] = Field(None, alias="contactPhone")
  1134. institution_logo: Optional[str] = Field(None, alias="institutionLogo")
  1135. status: Optional[str] = Field("1", alias="status")
  1136. order_num: Optional[int] = Field(0, alias="orderNum")
  1137. remark: Optional[str] = None
  1138. class CheckupInstitutionUpdateBody(BaseModel):
  1139. model_config = ConfigDict(populate_by_name=True)
  1140. institution_name: Optional[str] = Field(None, alias="institutionName")
  1141. institution_address: Optional[str] = Field(None, alias="institutionAddress")
  1142. contact_name: Optional[str] = Field(None, alias="contactName")
  1143. contact_phone: Optional[str] = Field(None, alias="contactPhone")
  1144. institution_logo: Optional[str] = Field(None, alias="institutionLogo")
  1145. status: Optional[str] = Field(None, alias="status")
  1146. order_num: Optional[int] = Field(None, alias="orderNum")
  1147. remark: Optional[str] = None
  1148. @router.get("/checkup-institutions")
  1149. async def list_checkup_institutions(
  1150. page: int = Query(1, ge=1),
  1151. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  1152. keyword: Optional[str] = None,
  1153. status: Optional[str] = None,
  1154. user: dict = Depends(require_admin_or_super_admin),
  1155. db: Session = Depends(get_db),
  1156. ):
  1157. tenant_no = _get_tenant_no(db, user)
  1158. items, total = ps_svc.list_checkup_institutions(
  1159. db,
  1160. page=page,
  1161. page_size=pageSize,
  1162. keyword=keyword,
  1163. status=status,
  1164. tenant_no=tenant_no,
  1165. user_info=user,
  1166. )
  1167. tp = ps_svc.total_pages(total, pageSize)
  1168. return success_response(
  1169. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  1170. )
  1171. @router.post("/checkup-institutions")
  1172. async def create_checkup_institution(
  1173. body: CheckupInstitutionCreateBody,
  1174. user: dict = Depends(require_admin_or_super_admin),
  1175. db: Session = Depends(get_db),
  1176. ):
  1177. try:
  1178. tenant_no = _get_tenant_no(db, user)
  1179. obj = ps_svc.create_checkup_institution(
  1180. db,
  1181. institution_name=body.institution_name,
  1182. institution_address=body.institution_address,
  1183. contact_name=body.contact_name,
  1184. contact_phone=body.contact_phone,
  1185. institution_logo=body.institution_logo,
  1186. status=body.status,
  1187. tenant_no=tenant_no,
  1188. order_num=body.order_num,
  1189. remark=body.remark,
  1190. user_id=user.get("user_id"),
  1191. )
  1192. return success_response(ps_svc._row_to_dict_checkup_institution(obj), message="创建成功")
  1193. except ValueError as e:
  1194. return error_response(400, str(e))
  1195. @router.put("/checkup-institutions/{row_id}")
  1196. async def update_checkup_institution(
  1197. row_id: str,
  1198. body: CheckupInstitutionUpdateBody,
  1199. user: dict = Depends(require_admin_or_super_admin),
  1200. db: Session = Depends(get_db),
  1201. ):
  1202. try:
  1203. tenant_no = _get_tenant_no(db, user)
  1204. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  1205. obj = ps_svc.update_checkup_institution(
  1206. db,
  1207. row_id,
  1208. institution_name=payload.get("institution_name"),
  1209. institution_address=payload.get("institution_address"),
  1210. contact_name=payload.get("contact_name"),
  1211. contact_phone=payload.get("contact_phone"),
  1212. institution_logo=payload.get("institution_logo"),
  1213. status=payload.get("status"),
  1214. order_num=payload.get("order_num"),
  1215. remark=payload.get("remark"),
  1216. user_id=user.get("user_id"),
  1217. tenant_no=tenant_no,
  1218. )
  1219. return success_response(ps_svc._row_to_dict_checkup_institution(obj), message="更新成功")
  1220. except ValueError as e:
  1221. return error_response(400, str(e))
  1222. @router.delete("/checkup-institutions/{row_id}")
  1223. async def delete_checkup_institution(
  1224. row_id: str,
  1225. user: dict = Depends(require_admin_or_super_admin),
  1226. db: Session = Depends(get_db),
  1227. ):
  1228. try:
  1229. tenant_no = _get_tenant_no(db, user)
  1230. ps_svc.soft_delete_checkup_institution(db, row_id, tenant_no=tenant_no)
  1231. return success_response(None, message="删除成功")
  1232. except ValueError as e:
  1233. return error_response(400, str(e))
  1234. # --- 体检机构开票信息 ---
  1235. class CheckupInstitutionInvoiceCreateBody(BaseModel):
  1236. model_config = ConfigDict(populate_by_name=True)
  1237. company_name: str = Field(..., alias="companyName")
  1238. tax_no: Optional[str] = Field(None, alias="taxNo")
  1239. company_address: Optional[str] = Field(None, alias="companyAddress")
  1240. company_phone: Optional[str] = Field(None, alias="companyPhone")
  1241. bank_name: Optional[str] = Field(None, alias="bankName")
  1242. bank_account: Optional[str] = Field(None, alias="bankAccount")
  1243. order_num: Optional[int] = Field(0, alias="orderNum")
  1244. remark: Optional[str] = None
  1245. class CheckupInstitutionInvoiceUpdateBody(BaseModel):
  1246. model_config = ConfigDict(populate_by_name=True)
  1247. company_name: Optional[str] = Field(None, alias="companyName")
  1248. tax_no: Optional[str] = Field(None, alias="taxNo")
  1249. company_address: Optional[str] = Field(None, alias="companyAddress")
  1250. company_phone: Optional[str] = Field(None, alias="companyPhone")
  1251. bank_name: Optional[str] = Field(None, alias="bankName")
  1252. bank_account: Optional[str] = Field(None, alias="bankAccount")
  1253. order_num: Optional[int] = Field(None, alias="orderNum")
  1254. remark: Optional[str] = None
  1255. @router.get("/checkup-institutions/{institution_id}/invoices")
  1256. async def list_checkup_institution_invoices(
  1257. institution_id: str,
  1258. user: dict = Depends(require_admin_or_super_admin),
  1259. db: Session = Depends(get_db),
  1260. ):
  1261. try:
  1262. tenant_no = _get_tenant_no(db, user)
  1263. items = ps_svc.list_checkup_institution_invoices(db, institution_id, tenant_no=tenant_no, user_info=user)
  1264. return success_response(items)
  1265. except ValueError as e:
  1266. return error_response(400, str(e))
  1267. @router.post("/checkup-institutions/{institution_id}/invoices")
  1268. async def create_checkup_institution_invoice(
  1269. institution_id: str,
  1270. body: CheckupInstitutionInvoiceCreateBody,
  1271. user: dict = Depends(require_admin_or_super_admin),
  1272. db: Session = Depends(get_db),
  1273. ):
  1274. try:
  1275. tenant_no = _get_tenant_no(db, user)
  1276. obj = ps_svc.create_checkup_institution_invoice(
  1277. db,
  1278. checkup_institution_id=institution_id,
  1279. company_name=body.company_name,
  1280. tax_no=body.tax_no,
  1281. company_address=body.company_address,
  1282. company_phone=body.company_phone,
  1283. bank_name=body.bank_name,
  1284. bank_account=body.bank_account,
  1285. tenant_no=tenant_no,
  1286. order_num=body.order_num,
  1287. remark=body.remark,
  1288. user_id=user.get("user_id"),
  1289. )
  1290. return success_response(ps_svc._row_to_dict_checkup_institution_invoice(obj), message="创建成功")
  1291. except ValueError as e:
  1292. return error_response(400, str(e))
  1293. @router.put("/checkup-institution-invoices/{invoice_id}")
  1294. async def update_checkup_institution_invoice(
  1295. invoice_id: str,
  1296. body: CheckupInstitutionInvoiceUpdateBody,
  1297. user: dict = Depends(require_admin_or_super_admin),
  1298. db: Session = Depends(get_db),
  1299. ):
  1300. try:
  1301. tenant_no = _get_tenant_no(db, user)
  1302. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  1303. obj = ps_svc.update_checkup_institution_invoice(
  1304. db,
  1305. invoice_id,
  1306. company_name=payload.get("company_name"),
  1307. tax_no=payload.get("tax_no"),
  1308. company_address=payload.get("company_address"),
  1309. company_phone=payload.get("company_phone"),
  1310. bank_name=payload.get("bank_name"),
  1311. bank_account=payload.get("bank_account"),
  1312. order_num=payload.get("order_num"),
  1313. remark=payload.get("remark"),
  1314. user_id=user.get("user_id"),
  1315. tenant_no=tenant_no,
  1316. )
  1317. return success_response(ps_svc._row_to_dict_checkup_institution_invoice(obj), message="更新成功")
  1318. except ValueError as e:
  1319. return error_response(400, str(e))
  1320. @router.delete("/checkup-institution-invoices/{invoice_id}")
  1321. async def delete_checkup_institution_invoice(
  1322. invoice_id: str,
  1323. user: dict = Depends(require_admin_or_super_admin),
  1324. db: Session = Depends(get_db),
  1325. ):
  1326. try:
  1327. tenant_no = _get_tenant_no(db, user)
  1328. ps_svc.soft_delete_checkup_institution_invoice(db, invoice_id, tenant_no=tenant_no)
  1329. return success_response(None, message="删除成功")
  1330. except ValueError as e:
  1331. return error_response(400, str(e))
  1332. # --- 体检机构项目及价格 ---
  1333. class CheckupInstitutionProjectCreateBody(BaseModel):
  1334. model_config = ConfigDict(populate_by_name=True)
  1335. project_name: str = Field(..., alias="projectName")
  1336. project_fee: Optional[float] = Field(None, alias="projectFee")
  1337. status: Optional[str] = Field("1", alias="status")
  1338. order_num: Optional[int] = Field(0, alias="orderNum")
  1339. remark: Optional[str] = None
  1340. class CheckupInstitutionProjectUpdateBody(BaseModel):
  1341. model_config = ConfigDict(populate_by_name=True)
  1342. project_name: Optional[str] = Field(None, alias="projectName")
  1343. project_fee: Optional[float] = Field(None, alias="projectFee")
  1344. status: Optional[str] = Field(None, alias="status")
  1345. order_num: Optional[int] = Field(None, alias="orderNum")
  1346. remark: Optional[str] = None
  1347. @router.get("/checkup-institutions/{institution_id}/projects")
  1348. async def list_checkup_institution_projects(
  1349. institution_id: str,
  1350. user: dict = Depends(require_admin_or_super_admin),
  1351. db: Session = Depends(get_db),
  1352. ):
  1353. try:
  1354. tenant_no = _get_tenant_no(db, user)
  1355. items = ps_svc.list_checkup_institution_projects(db, institution_id, tenant_no=tenant_no, user_info=user)
  1356. return success_response(items)
  1357. except ValueError as e:
  1358. return error_response(400, str(e))
  1359. @router.post("/checkup-institutions/{institution_id}/projects")
  1360. async def create_checkup_institution_project(
  1361. institution_id: str,
  1362. body: CheckupInstitutionProjectCreateBody,
  1363. user: dict = Depends(require_admin_or_super_admin),
  1364. db: Session = Depends(get_db),
  1365. ):
  1366. try:
  1367. tenant_no = _get_tenant_no(db, user)
  1368. obj = ps_svc.create_checkup_institution_project(
  1369. db,
  1370. checkup_institution_id=institution_id,
  1371. project_name=body.project_name,
  1372. project_fee=body.project_fee,
  1373. status=body.status,
  1374. tenant_no=tenant_no,
  1375. order_num=body.order_num,
  1376. remark=body.remark,
  1377. user_id=user.get("user_id"),
  1378. )
  1379. return success_response(ps_svc._row_to_dict_checkup_institution_project(obj), message="创建成功")
  1380. except ValueError as e:
  1381. return error_response(400, str(e))
  1382. @router.put("/checkup-institution-projects/{project_id}")
  1383. async def update_checkup_institution_project(
  1384. project_id: str,
  1385. body: CheckupInstitutionProjectUpdateBody,
  1386. user: dict = Depends(require_admin_or_super_admin),
  1387. db: Session = Depends(get_db),
  1388. ):
  1389. try:
  1390. tenant_no = _get_tenant_no(db, user)
  1391. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  1392. obj = ps_svc.update_checkup_institution_project(
  1393. db,
  1394. project_id,
  1395. project_name=payload.get("project_name"),
  1396. project_fee=payload.get("project_fee"),
  1397. status=payload.get("status"),
  1398. order_num=payload.get("order_num"),
  1399. remark=payload.get("remark"),
  1400. user_id=user.get("user_id"),
  1401. tenant_no=tenant_no,
  1402. )
  1403. return success_response(ps_svc._row_to_dict_checkup_institution_project(obj), message="更新成功")
  1404. except ValueError as e:
  1405. return error_response(400, str(e))
  1406. @router.delete("/checkup-institution-projects/{project_id}")
  1407. async def delete_checkup_institution_project(
  1408. project_id: str,
  1409. user: dict = Depends(require_admin_or_super_admin),
  1410. db: Session = Depends(get_db),
  1411. ):
  1412. try:
  1413. tenant_no = _get_tenant_no(db, user)
  1414. ps_svc.soft_delete_checkup_institution_project(db, project_id, tenant_no=tenant_no)
  1415. return success_response(None, message="删除成功")
  1416. except ValueError as e:
  1417. return error_response(400, str(e))
  1418. # ---------------------------------------------------------------------------
  1419. # 7. 渠道服务购买订单
  1420. # ---------------------------------------------------------------------------
  1421. class ServiceOrderProductItem(BaseModel):
  1422. model_config = ConfigDict(populate_by_name=True)
  1423. project_code: Optional[str] = Field(None, alias="projectCode")
  1424. project_name: Optional[str] = Field(None, alias="projectName")
  1425. cell_type: Optional[str] = Field(None, alias="cellType")
  1426. spec_text: Optional[str] = Field(None, alias="specText")
  1427. agreement_unit_price: Optional[float] = Field(None, alias="agreementUnitPrice")
  1428. buy_qty: Optional[float] = Field(None, alias="buyQty")
  1429. buy_amount: Optional[float] = Field(None, alias="buyAmount")
  1430. class ServiceOrderCreateBody(BaseModel):
  1431. model_config = ConfigDict(populate_by_name=True)
  1432. channel_id: str = Field(..., alias="channelId")
  1433. order_type: str = Field(..., alias="orderType") # yfk / gmcp
  1434. order_name: Optional[str] = Field(None, alias="orderName")
  1435. dept_name: Optional[str] = Field(None, alias="deptName")
  1436. prepay_amount: Optional[float] = Field(None, alias="prepayAmount")
  1437. channel_agreement_id: Optional[str] = Field(None, alias="channelAgreementId")
  1438. products: Optional[List[ServiceOrderProductItem]] = None
  1439. remark: Optional[str] = None
  1440. class ServiceOrderUpdateBody(BaseModel):
  1441. model_config = ConfigDict(populate_by_name=True)
  1442. dept_name: Optional[str] = Field(None, alias="deptName")
  1443. order_name: Optional[str] = Field(None, alias="orderName")
  1444. remark: Optional[str] = None
  1445. prepay_amount: Optional[float] = Field(None, alias="prepayAmount")
  1446. channel_agreement_id: Optional[str] = Field(None, alias="channelAgreementId")
  1447. products: Optional[List[ServiceOrderProductItem]] = None
  1448. class FinanceSubmitBody(BaseModel):
  1449. model_config = ConfigDict(populate_by_name=True)
  1450. finance_submit_remark: Optional[str] = Field(None, alias="financeSubmitRemark")
  1451. finance_submit_attachments: Optional[str] = Field(None, alias="financeSubmitAttachments")
  1452. class FinanceConfirmBody(BaseModel):
  1453. model_config = ConfigDict(populate_by_name=True)
  1454. finance_remark: Optional[str] = Field(None, alias="financeRemark")
  1455. finance_confirm_attachments: Optional[str] = Field(None, alias="financeConfirmAttachments")
  1456. class RefundSubmitBody(BaseModel):
  1457. model_config = ConfigDict(populate_by_name=True)
  1458. refund_amount: Optional[float] = Field(None, alias="refundAmount")
  1459. refund_remark: Optional[str] = Field(None, alias="refundRemark")
  1460. refund_attachments: Optional[str] = Field(None, alias="refundAttachments")
  1461. class RefundConfirmBody(BaseModel):
  1462. model_config = ConfigDict(populate_by_name=True)
  1463. refund_finance_remark: Optional[str] = Field(None, alias="refundFinanceRemark")
  1464. refund_finance_confirm_attachments: Optional[str] = Field(
  1465. None, alias="refundFinanceConfirmAttachments"
  1466. )
  1467. class TxnRechargeBody(BaseModel):
  1468. model_config = ConfigDict(populate_by_name=True)
  1469. amount: float = Field(..., gt=0, alias="amount")
  1470. remark: Optional[str] = Field(None, alias="remark")
  1471. finance_submit_attachments: Optional[str] = Field(None, alias="financeSubmitAttachments")
  1472. class TxnRefundBody(BaseModel):
  1473. model_config = ConfigDict(populate_by_name=True)
  1474. amount: float = Field(..., gt=0, alias="amount")
  1475. remark: Optional[str] = Field(None, alias="remark")
  1476. attachments: Optional[str] = Field(None, alias="attachments")
  1477. class TxnProductItem(BaseModel):
  1478. model_config = ConfigDict(populate_by_name=True)
  1479. project_code: Optional[str] = Field(None, alias="projectCode")
  1480. project_name: Optional[str] = Field(None, alias="projectName")
  1481. cell_type: Optional[str] = Field(None, alias="cellType")
  1482. spec_text: Optional[str] = Field(None, alias="specText")
  1483. agreement_unit_price: Optional[float] = Field(None, alias="agreementUnitPrice")
  1484. qty: Optional[float] = Field(None, alias="qty")
  1485. amount: Optional[float] = Field(None, alias="amount")
  1486. class TxnPurchaseBody(BaseModel):
  1487. model_config = ConfigDict(populate_by_name=True)
  1488. products: List[TxnProductItem] = Field(..., alias="products")
  1489. remark: Optional[str] = Field(None, alias="remark")
  1490. class TxnUnsubscribeBody(BaseModel):
  1491. model_config = ConfigDict(populate_by_name=True)
  1492. products: List[TxnProductItem] = Field(..., alias="products")
  1493. remark: Optional[str] = Field(None, alias="remark")
  1494. class TxnUpdateBody(BaseModel):
  1495. model_config = ConfigDict(populate_by_name=True)
  1496. amount: Optional[float] = Field(None, gt=0, alias="amount")
  1497. remark: Optional[str] = Field(None, alias="remark")
  1498. finance_submit_attachments: Optional[str] = Field(None, alias="financeSubmitAttachments")
  1499. refund_attachments: Optional[str] = Field(None, alias="refundAttachments")
  1500. products: Optional[List[TxnProductItem]] = Field(None, alias="products")
  1501. class TxnFinanceSubmitBody(BaseModel):
  1502. model_config = ConfigDict(populate_by_name=True)
  1503. finance_submit_remark: Optional[str] = Field(None, alias="financeSubmitRemark")
  1504. finance_submit_attachments: Optional[str] = Field(None, alias="financeSubmitAttachments")
  1505. class TxnFinanceConfirmBody(BaseModel):
  1506. model_config = ConfigDict(populate_by_name=True)
  1507. finance_remark: Optional[str] = Field(None, alias="financeRemark")
  1508. finance_confirm_attachments: Optional[str] = Field(None, alias="financeConfirmAttachments")
  1509. @router.get("/channel-service-orders")
  1510. async def list_channel_service_orders(
  1511. page: int = Query(1, ge=1),
  1512. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  1513. orderType: Optional[str] = Query(None, alias="orderType"),
  1514. orderStatus: Optional[str] = Query(None, alias="orderStatus"),
  1515. channelId: Optional[str] = Query(None, alias="channelId"),
  1516. keyword: Optional[str] = None,
  1517. createTimeStart: Optional[str] = Query(None, alias="createTimeStart"),
  1518. createTimeEnd: Optional[str] = Query(None, alias="createTimeEnd"),
  1519. user: dict = Depends(require_admin_or_super_admin),
  1520. db: Session = Depends(get_db),
  1521. ):
  1522. tenant_no = _get_tenant_no(db, user)
  1523. items, total = ps_svc.list_channel_service_orders(
  1524. db,
  1525. page=page,
  1526. page_size=pageSize,
  1527. order_type=orderType,
  1528. order_status=orderStatus,
  1529. channel_id=channelId,
  1530. keyword=keyword,
  1531. create_time_start=createTimeStart,
  1532. create_time_end=createTimeEnd,
  1533. tenant_no=tenant_no,
  1534. user_info=user,
  1535. )
  1536. tp = ps_svc.total_pages(total, pageSize)
  1537. return success_response(
  1538. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  1539. )
  1540. @router.get("/channel-service-orders/{order_id}")
  1541. async def get_channel_service_order_detail(
  1542. order_id: str,
  1543. user: dict = Depends(require_admin_or_super_admin),
  1544. db: Session = Depends(get_db),
  1545. ):
  1546. try:
  1547. tenant_no = _get_tenant_no(db, user)
  1548. order = ps_svc.get_service_order(db, order_id, tenant_no=tenant_no)
  1549. if not order:
  1550. return error_response(404, "订单不存在")
  1551. order_dict = ps_svc._row_to_dict_service_order(order, db, tenant_no)
  1552. user_ids = [x for x in (order.create_by, order.finance_submit_by) if x]
  1553. name_map = ps_svc._user_nick_name_map(db, user_ids)
  1554. order_dict["createByName"] = name_map.get(order.create_by or "", "") or (order.create_by or "")
  1555. order_dict["financeSubmitByName"] = name_map.get(order.finance_submit_by or "", "") or (
  1556. order.finance_submit_by or ""
  1557. )
  1558. products = ps_svc.get_service_order_products(db, order_id, tenant_no=tenant_no)
  1559. order_dict["products"] = products
  1560. txns, _ = ps_svc.list_service_order_txns(db, service_order_id=order_id, tenant_no=tenant_no, page_size=200)
  1561. order_dict["transactions"] = txns
  1562. if order.order_type == "gmcp":
  1563. has_lock = ps_svc._gmcp_has_purchase_lock(db, order_id, tenant_no)
  1564. order_dict["hasPurchaseTxn"] = has_lock
  1565. order_dict["agreementEditable"] = ps_svc.gmcp_agreement_editable(db, order, tenant_no)
  1566. else:
  1567. order_dict["hasPurchaseTxn"] = False
  1568. order_dict["agreementEditable"] = order.order_status == "dsx"
  1569. return success_response(order_dict)
  1570. except ValueError as e:
  1571. return error_response(400, str(e))
  1572. @router.post("/channel-service-orders")
  1573. async def create_channel_service_order(
  1574. body: ServiceOrderCreateBody,
  1575. user: dict = Depends(require_admin_or_super_admin),
  1576. db: Session = Depends(get_db),
  1577. ):
  1578. try:
  1579. tenant_no = _get_tenant_no(db, user)
  1580. products_data = None
  1581. if body.products:
  1582. products_data = [p.model_dump(by_alias=False) for p in body.products]
  1583. order = ps_svc.create_channel_service_order(
  1584. db,
  1585. channel_id=body.channel_id,
  1586. order_type=body.order_type,
  1587. order_name=body.order_name,
  1588. dept_name=body.dept_name,
  1589. prepay_amount=body.prepay_amount,
  1590. channel_agreement_id=body.channel_agreement_id,
  1591. products=products_data,
  1592. tenant_no=tenant_no,
  1593. remark=body.remark,
  1594. user_id=user.get("user_id"),
  1595. )
  1596. order_dict = ps_svc._row_to_dict_service_order(order, db, tenant_no)
  1597. order_dict["products"] = ps_svc.get_service_order_products(db, order.id, tenant_no=tenant_no)
  1598. return success_response(order_dict, message="创建成功")
  1599. except ValueError as e:
  1600. return error_response(400, str(e))
  1601. @router.put("/channel-service-orders/{order_id}")
  1602. async def update_channel_service_order(
  1603. order_id: str,
  1604. body: ServiceOrderUpdateBody,
  1605. user: dict = Depends(require_admin_or_super_admin),
  1606. db: Session = Depends(get_db),
  1607. ):
  1608. try:
  1609. tenant_no = _get_tenant_no(db, user)
  1610. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  1611. products_data = None
  1612. if "products" in payload:
  1613. products_data = payload.pop("products")
  1614. if products_data is not None:
  1615. products_data = [
  1616. p.model_dump(by_alias=False) if hasattr(p, "model_dump") else p
  1617. for p in (body.products or [])
  1618. ]
  1619. obj = ps_svc.update_channel_service_order(
  1620. db,
  1621. order_id,
  1622. tenant_no=tenant_no,
  1623. user_id=user.get("user_id"),
  1624. products=products_data,
  1625. **payload,
  1626. )
  1627. return success_response(
  1628. ps_svc._row_to_dict_service_order(obj, db, tenant_no), message="更新成功"
  1629. )
  1630. except ValueError as e:
  1631. return error_response(400, str(e))
  1632. @router.delete("/channel-service-orders/{order_id}")
  1633. async def delete_channel_service_order(
  1634. order_id: str,
  1635. user: dict = Depends(require_admin_or_super_admin),
  1636. db: Session = Depends(get_db),
  1637. ):
  1638. try:
  1639. tenant_no = _get_tenant_no(db, user)
  1640. ps_svc.soft_delete_service_order(db, order_id, tenant_no=tenant_no)
  1641. return success_response(None, message="删除成功")
  1642. except ValueError as e:
  1643. return error_response(400, str(e))
  1644. @router.post("/channel-service-orders/{order_id}/submit-finance-confirm")
  1645. async def submit_service_order_finance_confirm(
  1646. order_id: str,
  1647. body: FinanceSubmitBody,
  1648. user: dict = Depends(require_admin_or_super_admin),
  1649. db: Session = Depends(get_db),
  1650. ):
  1651. try:
  1652. tenant_no = _get_tenant_no(db, user)
  1653. obj = ps_svc.submit_finance_confirm(
  1654. db,
  1655. order_id,
  1656. finance_submit_remark=body.finance_submit_remark,
  1657. finance_submit_attachments=body.finance_submit_attachments,
  1658. tenant_no=tenant_no,
  1659. user_id=user.get("user_id"),
  1660. )
  1661. return success_response(
  1662. ps_svc._row_to_dict_service_order(obj, db, tenant_no), message="已发起财务确认"
  1663. )
  1664. except ValueError as e:
  1665. return error_response(400, str(e))
  1666. @router.post("/channel-service-orders/{order_id}/confirm-receipt")
  1667. async def confirm_service_order_receipt(
  1668. order_id: str,
  1669. body: FinanceConfirmBody,
  1670. user: dict = Depends(require_admin_or_super_admin),
  1671. db: Session = Depends(get_db),
  1672. ):
  1673. try:
  1674. tenant_no = _get_tenant_no(db, user)
  1675. obj = ps_svc.confirm_receipt(
  1676. db,
  1677. order_id,
  1678. finance_remark=body.finance_remark,
  1679. finance_confirm_attachments=body.finance_confirm_attachments,
  1680. tenant_no=tenant_no,
  1681. user_id=user.get("user_id"),
  1682. )
  1683. return success_response(
  1684. ps_svc._row_to_dict_service_order(obj, db, tenant_no), message="确认收款成功"
  1685. )
  1686. except ValueError as e:
  1687. return error_response(400, str(e))
  1688. @router.post("/channel-service-orders/{order_id}/submit-refund")
  1689. async def submit_service_order_refund(
  1690. order_id: str,
  1691. body: RefundSubmitBody,
  1692. user: dict = Depends(require_admin_or_super_admin),
  1693. db: Session = Depends(get_db),
  1694. ):
  1695. try:
  1696. tenant_no = _get_tenant_no(db, user)
  1697. obj = ps_svc.submit_refund(
  1698. db,
  1699. order_id,
  1700. refund_amount=body.refund_amount,
  1701. refund_remark=body.refund_remark,
  1702. refund_attachments=body.refund_attachments,
  1703. tenant_no=tenant_no,
  1704. user_id=user.get("user_id"),
  1705. )
  1706. return success_response(
  1707. ps_svc._row_to_dict_service_order(obj, db, tenant_no), message="提交退款成功"
  1708. )
  1709. except ValueError as e:
  1710. return error_response(400, str(e))
  1711. @router.post("/channel-service-orders/{order_id}/confirm-refund")
  1712. async def confirm_service_order_refund(
  1713. order_id: str,
  1714. body: RefundConfirmBody,
  1715. user: dict = Depends(require_admin_or_super_admin),
  1716. db: Session = Depends(get_db),
  1717. ):
  1718. try:
  1719. tenant_no = _get_tenant_no(db, user)
  1720. obj = ps_svc.confirm_refund(
  1721. db,
  1722. order_id,
  1723. refund_finance_remark=body.refund_finance_remark,
  1724. refund_finance_confirm_attachments=body.refund_finance_confirm_attachments,
  1725. tenant_no=tenant_no,
  1726. user_id=user.get("user_id"),
  1727. )
  1728. return success_response(
  1729. ps_svc._row_to_dict_service_order(obj, db, tenant_no), message="确认退款成功"
  1730. )
  1731. except ValueError as e:
  1732. return error_response(400, str(e))
  1733. @router.put("/channel-service-orders/{order_id}/void")
  1734. async def void_channel_service_order(
  1735. order_id: str,
  1736. user: dict = Depends(require_admin_or_super_admin),
  1737. db: Session = Depends(get_db),
  1738. ):
  1739. try:
  1740. tenant_no = _get_tenant_no(db, user)
  1741. obj = ps_svc.void_service_order(
  1742. db,
  1743. order_id,
  1744. tenant_no=tenant_no,
  1745. user_id=user.get("user_id"),
  1746. )
  1747. return success_response(
  1748. ps_svc._row_to_dict_service_order(obj, db, tenant_no), message="作废成功"
  1749. )
  1750. except ValueError as e:
  1751. return error_response(400, str(e))
  1752. @router.get("/channel-service-order-txns")
  1753. async def list_service_order_txns(
  1754. page: int = Query(1, ge=1),
  1755. pageSize: int = Query(20, ge=1, le=100, alias="pageSize"),
  1756. serviceOrderId: Optional[str] = Query(None, alias="serviceOrderId"),
  1757. channelId: Optional[str] = Query(None, alias="channelId"),
  1758. txnStatus: Optional[str] = Query(None, alias="txnStatus"),
  1759. txnType: Optional[str] = Query(None, alias="txnType"),
  1760. keyword: Optional[str] = None,
  1761. createTimeStart: Optional[str] = Query(None, alias="createTimeStart"),
  1762. createTimeEnd: Optional[str] = Query(None, alias="createTimeEnd"),
  1763. financeDoneOnly: Optional[bool] = Query(False, alias="financeDoneOnly"),
  1764. user: dict = Depends(require_admin_or_super_admin),
  1765. db: Session = Depends(get_db),
  1766. ):
  1767. tenant_no = _get_tenant_no(db, user)
  1768. items, total = ps_svc.list_service_order_txns(
  1769. db,
  1770. service_order_id=serviceOrderId,
  1771. channel_id=channelId,
  1772. txn_status=txnStatus,
  1773. txn_type=txnType,
  1774. keyword=keyword,
  1775. create_time_start=createTimeStart,
  1776. create_time_end=createTimeEnd,
  1777. finance_done_only=bool(financeDoneOnly),
  1778. page=page,
  1779. page_size=pageSize,
  1780. tenant_no=tenant_no,
  1781. )
  1782. tp = ps_svc.total_pages(total, pageSize)
  1783. return success_response(
  1784. {"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp}
  1785. )
  1786. @router.get("/channel-service-order-txns/{txn_id}")
  1787. async def get_service_order_txn_detail(
  1788. txn_id: str,
  1789. user: dict = Depends(require_admin_or_super_admin),
  1790. db: Session = Depends(get_db),
  1791. ):
  1792. try:
  1793. tenant_no = _get_tenant_no(db, user)
  1794. txn = ps_svc.get_service_order_txn(db, txn_id, tenant_no=tenant_no)
  1795. if not txn:
  1796. return error_response(404, "流水不存在")
  1797. txn_dict = ps_svc._service_order_txn_to_response_dict(db, txn, tenant_no)
  1798. return success_response(txn_dict)
  1799. except ValueError as e:
  1800. return error_response(400, str(e))
  1801. @router.post("/channel-service-orders/{order_id}/recharge")
  1802. async def create_recharge_txn(
  1803. order_id: str,
  1804. body: TxnRechargeBody,
  1805. user: dict = Depends(require_admin_or_super_admin),
  1806. db: Session = Depends(get_db),
  1807. ):
  1808. try:
  1809. tenant_no = _get_tenant_no(db, user)
  1810. txn = ps_svc.create_recharge_txn(
  1811. db,
  1812. order_id,
  1813. amount=body.amount,
  1814. remark=body.remark,
  1815. finance_submit_attachments=body.finance_submit_attachments,
  1816. tenant_no=tenant_no,
  1817. user_id=user.get("user_id"),
  1818. )
  1819. return success_response(
  1820. ps_svc._row_to_dict_service_order_txn(txn, db, tenant_no), message="充值流水创建成功"
  1821. )
  1822. except ValueError as e:
  1823. return error_response(400, str(e))
  1824. @router.post("/channel-service-orders/{order_id}/refund")
  1825. async def create_refund_txn(
  1826. order_id: str,
  1827. body: TxnRefundBody,
  1828. user: dict = Depends(require_admin_or_super_admin),
  1829. db: Session = Depends(get_db),
  1830. ):
  1831. try:
  1832. tenant_no = _get_tenant_no(db, user)
  1833. txn = ps_svc.create_refund_txn(
  1834. db,
  1835. order_id,
  1836. amount=body.amount,
  1837. remark=body.remark,
  1838. attachments=body.attachments,
  1839. tenant_no=tenant_no,
  1840. user_id=user.get("user_id"),
  1841. )
  1842. return success_response(
  1843. ps_svc._row_to_dict_service_order_txn(txn, db, tenant_no), message="退款流水创建成功"
  1844. )
  1845. except ValueError as e:
  1846. return error_response(400, str(e))
  1847. @router.post("/channel-service-orders/{order_id}/purchase")
  1848. async def create_purchase_txn(
  1849. order_id: str,
  1850. body: TxnPurchaseBody,
  1851. user: dict = Depends(require_admin_or_super_admin),
  1852. db: Session = Depends(get_db),
  1853. ):
  1854. try:
  1855. tenant_no = _get_tenant_no(db, user)
  1856. products_data = [p.model_dump(by_alias=False) for p in body.products]
  1857. txn = ps_svc.create_purchase_txn(
  1858. db,
  1859. order_id,
  1860. products=products_data,
  1861. remark=body.remark,
  1862. tenant_no=tenant_no,
  1863. user_id=user.get("user_id"),
  1864. )
  1865. return success_response(
  1866. ps_svc._row_to_dict_service_order_txn(txn, db, tenant_no), message="购买产品流水创建成功"
  1867. )
  1868. except ValueError as e:
  1869. return error_response(400, str(e))
  1870. @router.post("/channel-service-orders/{order_id}/unsubscribe")
  1871. async def create_unsubscribe_txn(
  1872. order_id: str,
  1873. body: TxnUnsubscribeBody,
  1874. user: dict = Depends(require_admin_or_super_admin),
  1875. db: Session = Depends(get_db),
  1876. ):
  1877. try:
  1878. tenant_no = _get_tenant_no(db, user)
  1879. products_data = [p.model_dump(by_alias=False) for p in body.products]
  1880. txn = ps_svc.create_unsubscribe_txn(
  1881. db,
  1882. order_id,
  1883. products=products_data,
  1884. remark=body.remark,
  1885. tenant_no=tenant_no,
  1886. user_id=user.get("user_id"),
  1887. )
  1888. return success_response(
  1889. ps_svc._row_to_dict_service_order_txn(txn, db, tenant_no), message="退订产品流水创建成功"
  1890. )
  1891. except ValueError as e:
  1892. return error_response(400, str(e))
  1893. @router.put("/channel-service-order-txns/{txn_id}")
  1894. async def update_service_order_txn(
  1895. txn_id: str,
  1896. body: TxnUpdateBody,
  1897. user: dict = Depends(require_admin_or_super_admin),
  1898. db: Session = Depends(get_db),
  1899. ):
  1900. try:
  1901. tenant_no = _get_tenant_no(db, user)
  1902. products_data = None
  1903. if body.products is not None:
  1904. products_data = [p.model_dump(by_alias=False) for p in body.products]
  1905. txn = ps_svc.update_service_order_txn(
  1906. db,
  1907. txn_id,
  1908. amount=body.amount,
  1909. remark=body.remark,
  1910. finance_submit_attachments=body.finance_submit_attachments,
  1911. refund_attachments=body.refund_attachments,
  1912. products=products_data,
  1913. tenant_no=tenant_no,
  1914. user_id=user.get("user_id"),
  1915. )
  1916. return success_response(
  1917. ps_svc._row_to_dict_service_order_txn(txn, db, tenant_no), message="更新成功"
  1918. )
  1919. except ValueError as e:
  1920. return error_response(400, str(e))
  1921. @router.post("/channel-service-order-txns/{txn_id}/submit-finance")
  1922. async def submit_txn_finance(
  1923. txn_id: str,
  1924. body: TxnFinanceSubmitBody,
  1925. user: dict = Depends(require_admin_or_super_admin),
  1926. db: Session = Depends(get_db),
  1927. ):
  1928. try:
  1929. tenant_no = _get_tenant_no(db, user)
  1930. txn = ps_svc.submit_txn_finance(
  1931. db,
  1932. txn_id,
  1933. finance_submit_remark=body.finance_submit_remark,
  1934. finance_submit_attachments=body.finance_submit_attachments,
  1935. tenant_no=tenant_no,
  1936. user_id=user.get("user_id"),
  1937. )
  1938. return success_response(
  1939. ps_svc._row_to_dict_service_order_txn(txn, db, tenant_no), message="已发起财务确认"
  1940. )
  1941. except ValueError as e:
  1942. return error_response(400, str(e))
  1943. @router.post("/channel-service-order-txns/{txn_id}/confirm")
  1944. async def confirm_txn(
  1945. txn_id: str,
  1946. body: TxnFinanceConfirmBody,
  1947. user: dict = Depends(require_admin_or_super_admin),
  1948. db: Session = Depends(get_db),
  1949. ):
  1950. try:
  1951. tenant_no = _get_tenant_no(db, user)
  1952. txn = ps_svc.confirm_txn(
  1953. db,
  1954. txn_id,
  1955. finance_remark=body.finance_remark,
  1956. finance_confirm_attachments=body.finance_confirm_attachments,
  1957. tenant_no=tenant_no,
  1958. user_id=user.get("user_id"),
  1959. )
  1960. return success_response(
  1961. ps_svc._service_order_txn_to_response_dict(db, txn, tenant_no),
  1962. message="财务确认成功",
  1963. )
  1964. except ValueError as e:
  1965. return error_response(400, str(e))
  1966. @router.put("/channel-service-order-txns/{txn_id}/void")
  1967. async def void_txn(
  1968. txn_id: str,
  1969. user: dict = Depends(require_admin_or_super_admin),
  1970. db: Session = Depends(get_db),
  1971. ):
  1972. try:
  1973. tenant_no = _get_tenant_no(db, user)
  1974. txn = ps_svc.void_txn(
  1975. db,
  1976. txn_id,
  1977. tenant_no=tenant_no,
  1978. user_id=user.get("user_id"),
  1979. )
  1980. return success_response(
  1981. ps_svc._service_order_txn_to_response_dict(db, txn, tenant_no),
  1982. message="作废成功",
  1983. )
  1984. except ValueError as e:
  1985. return error_response(400, str(e))
  1986. @router.get("/channels/{channel_id}/account")
  1987. async def get_channel_account_info(
  1988. channel_id: str,
  1989. user: dict = Depends(require_admin_or_super_admin),
  1990. db: Session = Depends(get_db),
  1991. ):
  1992. try:
  1993. tenant_no = _get_tenant_no(db, user)
  1994. account = ps_svc.get_channel_account(db, channel_id, tenant_no=tenant_no)
  1995. if not account:
  1996. return success_response({"channelId": channel_id, "balanceAmount": 0.0})
  1997. return success_response(ps_svc._row_to_dict_channel_account(account))
  1998. except ValueError as e:
  1999. return error_response(400, str(e))
  2000. @router.get("/channels/{channel_id}/product-quotas")
  2001. async def list_channel_product_quotas(
  2002. channel_id: str,
  2003. user: dict = Depends(require_admin_or_super_admin),
  2004. db: Session = Depends(get_db),
  2005. ):
  2006. try:
  2007. tenant_no = _get_tenant_no(db, user)
  2008. items = ps_svc.list_channel_product_quotas(db, channel_id, tenant_no=tenant_no)
  2009. return success_response(items)
  2010. except ValueError as e:
  2011. return error_response(400, str(e))
  2012. # --- 体检机构项目套餐 ---
  2013. class CheckupPackageItemBody(BaseModel):
  2014. model_config = ConfigDict(populate_by_name=True)
  2015. checkup_institution_project_id: str = Field(..., alias="checkupInstitutionProjectId")
  2016. project_name: Optional[str] = Field(None, alias="projectName")
  2017. project_fee: Optional[float] = Field(None, alias="projectFee")
  2018. order_num: Optional[int] = Field(None, alias="orderNum")
  2019. remark: Optional[str] = None
  2020. class CheckupInstitutionPackageCreateBody(BaseModel):
  2021. model_config = ConfigDict(populate_by_name=True)
  2022. package_name: str = Field(..., alias="packageName")
  2023. package_code: str = Field(..., alias="packageCode")
  2024. status: Optional[str] = Field("1", alias="status")
  2025. order_num: Optional[int] = Field(0, alias="orderNum")
  2026. remark: Optional[str] = None
  2027. items: List[CheckupPackageItemBody] = Field(default_factory=list)
  2028. class CheckupInstitutionPackageUpdateBody(BaseModel):
  2029. model_config = ConfigDict(populate_by_name=True)
  2030. package_name: Optional[str] = Field(None, alias="packageName")
  2031. package_code: Optional[str] = Field(None, alias="packageCode")
  2032. status: Optional[str] = Field(None, alias="status")
  2033. order_num: Optional[int] = Field(None, alias="orderNum")
  2034. remark: Optional[str] = None
  2035. items: Optional[List[CheckupPackageItemBody]] = None
  2036. def _package_items_to_crud(items) -> List[dict]:
  2037. out = []
  2038. for x in items:
  2039. if isinstance(x, dict):
  2040. out.append(
  2041. {
  2042. "checkup_institution_project_id": x.get("checkup_institution_project_id")
  2043. or x.get("checkupInstitutionProjectId"),
  2044. "project_name": x.get("project_name") or x.get("projectName"),
  2045. "project_fee": x.get("project_fee") if x.get("project_fee") is not None else x.get("projectFee"),
  2046. "order_num": x.get("order_num") if x.get("order_num") is not None else x.get("orderNum"),
  2047. "remark": x.get("remark"),
  2048. }
  2049. )
  2050. else:
  2051. out.append(
  2052. {
  2053. "checkup_institution_project_id": x.checkup_institution_project_id,
  2054. "project_name": x.project_name,
  2055. "project_fee": x.project_fee,
  2056. "order_num": x.order_num,
  2057. "remark": x.remark,
  2058. }
  2059. )
  2060. return out
  2061. @router.get("/checkup-institutions/{institution_id}/packages")
  2062. async def list_checkup_institution_packages(
  2063. institution_id: str,
  2064. user: dict = Depends(require_admin_or_super_admin),
  2065. db: Session = Depends(get_db),
  2066. ):
  2067. try:
  2068. tenant_no = _get_tenant_no(db, user)
  2069. items = ps_svc.list_checkup_institution_packages(db, institution_id, tenant_no=tenant_no, user_info=user)
  2070. return success_response(items)
  2071. except ValueError as e:
  2072. return error_response(400, str(e))
  2073. @router.post("/checkup-institutions/{institution_id}/packages")
  2074. async def create_checkup_institution_package(
  2075. institution_id: str,
  2076. body: CheckupInstitutionPackageCreateBody,
  2077. user: dict = Depends(require_admin_or_super_admin),
  2078. db: Session = Depends(get_db),
  2079. ):
  2080. try:
  2081. tenant_no = _get_tenant_no(db, user)
  2082. detail = ps_svc.create_checkup_institution_package(
  2083. db,
  2084. checkup_institution_id=institution_id,
  2085. package_name=body.package_name,
  2086. package_code=body.package_code,
  2087. status=body.status,
  2088. items=_package_items_to_crud(body.items),
  2089. tenant_no=tenant_no,
  2090. order_num=body.order_num,
  2091. remark=body.remark,
  2092. user_id=user.get("user_id"),
  2093. )
  2094. return success_response(detail, message="创建成功")
  2095. except ValueError as e:
  2096. return error_response(400, str(e))
  2097. @router.get("/checkup-institution-packages/{package_id}")
  2098. async def get_checkup_institution_package_detail(
  2099. package_id: str,
  2100. user: dict = Depends(require_admin_or_super_admin),
  2101. db: Session = Depends(get_db),
  2102. ):
  2103. try:
  2104. tenant_no = _get_tenant_no(db, user)
  2105. detail = ps_svc.get_checkup_institution_package_detail(db, package_id, tenant_no=tenant_no)
  2106. if not detail:
  2107. return error_response(404, "记录不存在")
  2108. return success_response(detail)
  2109. except ValueError as e:
  2110. return error_response(400, str(e))
  2111. @router.put("/checkup-institution-packages/{package_id}")
  2112. async def update_checkup_institution_package(
  2113. package_id: str,
  2114. body: CheckupInstitutionPackageUpdateBody,
  2115. user: dict = Depends(require_admin_or_super_admin),
  2116. db: Session = Depends(get_db),
  2117. ):
  2118. try:
  2119. tenant_no = _get_tenant_no(db, user)
  2120. payload = body.model_dump(exclude_unset=True, by_alias=False)
  2121. items_raw = payload.pop("items", None)
  2122. items = _package_items_to_crud(items_raw) if items_raw is not None else None
  2123. detail = ps_svc.update_checkup_institution_package(
  2124. db,
  2125. package_id,
  2126. package_name=payload.get("package_name"),
  2127. package_code=payload.get("package_code"),
  2128. status=payload.get("status"),
  2129. items=items,
  2130. order_num=payload.get("order_num"),
  2131. remark=payload.get("remark"),
  2132. user_id=user.get("user_id"),
  2133. tenant_no=tenant_no,
  2134. )
  2135. return success_response(detail, message="更新成功")
  2136. except ValueError as e:
  2137. return error_response(400, str(e))
  2138. @router.delete("/checkup-institution-packages/{package_id}")
  2139. async def delete_checkup_institution_package(
  2140. package_id: str,
  2141. user: dict = Depends(require_admin_or_super_admin),
  2142. db: Session = Depends(get_db),
  2143. ):
  2144. try:
  2145. tenant_no = _get_tenant_no(db, user)
  2146. ps_svc.soft_delete_checkup_institution_package(db, package_id, tenant_no=tenant_no)
  2147. return success_response(None, message="删除成功")
  2148. except ValueError as e:
  2149. return error_response(400, str(e))