Browse Source

fix: preserve mobile presign decimal precision.

zlwu 3 weeks ago
parent
commit
e0dc7f4cee
2 changed files with 206 additions and 9 deletions
  1. 73 8
      backend/app/services/market_presign.py
  2. 133 1
      backend/tests/test_market_presign_api.py

+ 73 - 8
backend/app/services/market_presign.py

@@ -2,6 +2,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 from datetime import datetime
 from datetime import datetime
+from decimal import Decimal
 from typing import List, Optional, Tuple
 from typing import List, Optional, Tuple
 
 
 from fastapi import HTTPException
 from fastapi import HTTPException
@@ -29,6 +30,70 @@ from app.services.market_field_scope import (
 from app.services.market_support import validate_attachment_refs
 from app.services.market_support import validate_attachment_refs
 
 
 
 
+_MONEY_QUANTUM = Decimal('0.01')
+
+
+def _money_text(value: Optional[Decimal]) -> Optional[str]:
+    if value is None:
+        return None
+    return format(Decimal(str(value)).quantize(_MONEY_QUANTUM), 'f')
+
+
+def _mobile_transfer_to_dict(
+    db: Session, row: BizPresignTransfer, tenant_no: str
+) -> dict:
+    data = presign_crud._transfer_to_dict(db, row, tenant_no)
+    data['transferAmount'] = _money_text(row.transfer_amount)
+    return data
+
+
+def _mobile_order_to_dict(
+    db: Session,
+    row: BizPresignOrder,
+    tenant_no: str,
+    *,
+    with_children: bool,
+    deposit_amount: Optional[Decimal] = None,
+    user_name_map: Optional[dict] = None,
+) -> dict:
+    deposit = (
+        deposit_amount
+        if deposit_amount is not None
+        else presign_crud.confirmed_deposit_amount(
+            db, presign_order_id=row.id, tenant_no=tenant_no
+        )
+    )
+    data = presign_crud.order_to_dict(
+        db,
+        row,
+        tenant_no,
+        with_children=False,
+        deposit_amount=deposit,
+        user_name_map=user_name_map,
+    )
+    data['contractAmount'] = _money_text(row.contract_amount)
+    data['confirmedDepositAmount'] = _money_text(deposit)
+    if with_children:
+        data['sampleTypeIds'] = presign_crud.list_sample_type_ids(
+            db, presign_order_id=row.id, tenant_no=tenant_no
+        )
+        transfer_rows = (
+            db.query(BizPresignTransfer)
+            .filter(
+                BizPresignTransfer.presign_order_id == row.id,
+                BizPresignTransfer.tenant_no == tenant_no,
+                BizPresignTransfer.del_flag == 0,
+            )
+            .order_by(BizPresignTransfer.create_time.asc())
+            .all()
+        )
+        data['transfers'] = [
+            _mobile_transfer_to_dict(db, transfer, tenant_no)
+            for transfer in transfer_rows
+        ]
+    return data
+
+
 def _dump_body(body: MarketPresignOrderBody) -> dict:
 def _dump_body(body: MarketPresignOrderBody) -> dict:
     return body.model_dump(by_alias=False, exclude_unset=True)
     return body.model_dump(by_alias=False, exclude_unset=True)
 
 
@@ -236,7 +301,7 @@ def list_presign_orders(
         db, person_ids, ctx.tenant_no
         db, person_ids, ctx.tenant_no
     )
     )
     return [
     return [
-        presign_crud.order_to_dict(
+        _mobile_order_to_dict(
             db,
             db,
             row,
             row,
             ctx.tenant_no,
             ctx.tenant_no,
@@ -258,7 +323,7 @@ def get_presign_order(
     row = assert_presign_access(
     row = assert_presign_access(
         db, order_id, ctx, user_info=user_info
         db, order_id, ctx, user_info=user_info
     )
     )
-    return presign_crud.order_to_dict(
+    return _mobile_order_to_dict(
         db, row, ctx.tenant_no, with_children=True
         db, row, ctx.tenant_no, with_children=True
     )
     )
 
 
@@ -347,7 +412,7 @@ def create_presign_order(
     except Exception:
     except Exception:
         db.rollback()
         db.rollback()
         raise
         raise
-    return presign_crud.order_to_dict(
+    return _mobile_order_to_dict(
         db, row, ctx.tenant_no, with_children=True
         db, row, ctx.tenant_no, with_children=True
     )
     )
 
 
@@ -414,7 +479,7 @@ def update_presign_order(
         row = assert_presign_access(
         row = assert_presign_access(
             db, row.id, ctx, user_info=user_info
             db, row.id, ctx, user_info=user_info
         )
         )
-        result = presign_crud.order_to_dict(
+        result = _mobile_order_to_dict(
             db, row, ctx.tenant_no, with_children=True
             db, row, ctx.tenant_no, with_children=True
         )
         )
         db.commit()
         db.commit()
@@ -538,7 +603,7 @@ def create_presign_transfer(
             create_dept_id=_current_dept_id(user_info),
             create_dept_id=_current_dept_id(user_info),
             commit=False,
             commit=False,
         )
         )
-        result = presign_crud._transfer_to_dict(db, row, ctx.tenant_no)
+        result = _mobile_transfer_to_dict(db, row, ctx.tenant_no)
         db.commit()
         db.commit()
         return result
         return result
     except Exception:
     except Exception:
@@ -576,7 +641,7 @@ def update_presign_transfer(
             fields=fields,
             fields=fields,
             commit=False,
             commit=False,
         )
         )
-        result = presign_crud._transfer_to_dict(db, row, ctx.tenant_no)
+        result = _mobile_transfer_to_dict(db, row, ctx.tenant_no)
         db.commit()
         db.commit()
         return result
         return result
     except Exception:
     except Exception:
@@ -626,7 +691,7 @@ def submit_presign_transfer(
             user_id=ctx.user_id,
             user_id=ctx.user_id,
             commit=False,
             commit=False,
         )
         )
-        result = presign_crud._transfer_to_dict(db, row, ctx.tenant_no)
+        result = _mobile_transfer_to_dict(db, row, ctx.tenant_no)
         db.commit()
         db.commit()
         return result
         return result
     except Exception:
     except Exception:
@@ -649,7 +714,7 @@ def withdraw_presign_transfer(
             user_id=ctx.user_id,
             user_id=ctx.user_id,
             commit=False,
             commit=False,
         )
         )
-        result = presign_crud._transfer_to_dict(db, row, ctx.tenant_no)
+        result = _mobile_transfer_to_dict(db, row, ctx.tenant_no)
         db.commit()
         db.commit()
         return result
         return result
     except Exception:
     except Exception:

+ 133 - 1
backend/tests/test_market_presign_api.py

@@ -1437,7 +1437,7 @@ def test_dfj_presign_transfer_can_edit_submit_withdraw_and_soft_delete():
         ctx=_ctx('specialist'),
         ctx=_ctx('specialist'),
         user_info=_user(),
         user_info=_user(),
     )
     )
-    assert updated['transferAmount'] == 200.25
+    assert updated['transferAmount'] == '200.25'
     assert updated['remark'] == '更新'
     assert updated['remark'] == '更新'
     assert (
     assert (
         db.query(BizPresignTransfer).filter_by(id=transfer_id).one().create_dept_id
         db.query(BizPresignTransfer).filter_by(id=transfer_id).one().create_dept_id
@@ -2172,3 +2172,135 @@ def test_market_presign_routes_are_fixed_and_expose_scoped_transfer_actions():
             }
             }
         )
         )
     assert transfer_actual == transfer_expected
     assert transfer_actual == transfer_expected
+
+
+def test_mobile_presign_money_dto_preserves_decimal_strings_for_presenter(
+    monkeypatch,
+):
+    import json
+    import subprocess
+    from decimal import Decimal
+    from pathlib import Path
+
+    from app.crud import presign_order as presign_crud
+    from app.services import market_presign
+
+    db = _session()
+    normal = _order('money-normal', sales_user_id='u1')
+    normal.contract_amount = Decimal('1234.50')
+    empty = _order('money-empty', sales_user_id='u1')
+    empty.contract_amount = None
+    paid = _transfer(
+        'money-paid', order_id=normal.id, pay_status='yjf'
+    )
+    paid.transfer_amount = Decimal('200.25')
+    draft = _transfer(
+        'money-draft', order_id=normal.id, pay_status='dfj'
+    )
+    draft.transfer_amount = Decimal('12.30')
+    db.add_all([normal, empty, paid, draft])
+    db.commit()
+
+    items, total = market_presign.list_presign_orders(
+        db,
+        ctx=_ctx('specialist'),
+        user_info=_user(),
+        page=1,
+        page_size=10,
+    )
+    by_id = {item['id']: item for item in items}
+    assert total == 2
+    assert by_id['money-normal']['contractAmount'] == '1234.50'
+    assert by_id['money-normal']['confirmedDepositAmount'] == '200.25'
+    assert by_id['money-empty']['contractAmount'] is None
+    assert by_id['money-empty']['confirmedDepositAmount'] == '0.00'
+
+    normal_detail = market_presign.get_presign_order(
+        db,
+        order_id=normal.id,
+        ctx=_ctx('specialist'),
+        user_info=_user(),
+    )
+    transfer_amounts = {
+        transfer['id']: transfer['transferAmount']
+        for transfer in normal_detail['transfers']
+    }
+    assert transfer_amounts == {
+        'money-paid': '200.25',
+        'money-draft': '12.30',
+    }
+
+    maximum = Decimal('9999999999999999.99')
+    maximum_order = _order('money-maximum', sales_user_id='u1')
+    maximum_order.contract_amount = maximum
+    monkeypatch.setattr(
+        market_presign,
+        'assert_presign_access',
+        lambda *_args, **_kwargs: maximum_order,
+    )
+    monkeypatch.setattr(
+        presign_crud,
+        'confirmed_deposit_amount',
+        lambda *_args, **_kwargs: maximum,
+    )
+    maximum_detail = market_presign.get_presign_order(
+        db,
+        order_id=maximum_order.id,
+        ctx=_ctx('specialist'),
+        user_info=_user(),
+    )
+    assert maximum_detail['contractAmount'] == str(maximum)
+    assert maximum_detail['confirmedDepositAmount'] == str(maximum)
+    maximum_transfer = _transfer(
+        'money-maximum-transfer', order_id=maximum_order.id
+    )
+    maximum_transfer.transfer_amount = maximum
+    maximum_transfer_dto = market_presign._mobile_transfer_to_dict(
+        db, maximum_transfer, 'tenant-a'
+    )
+    assert maximum_transfer_dto['transferAmount'] == str(maximum)
+
+    serialized = json.dumps(
+        {
+            'maximum': maximum_detail,
+            'maximumTransfer': maximum_transfer_dto,
+            'normal': normal_detail,
+            'empty': by_id['money-empty'],
+        },
+        ensure_ascii=False,
+    )
+    presenter_url = (
+        Path(__file__).resolve().parents[2]
+        / 'mobile-oa-h5/src/market/presign/presign-presenter.js'
+    ).as_uri()
+    script = """
+import fs from 'node:fs'
+const { formatPresignMoney } = await import(process.argv[1])
+const dto = JSON.parse(fs.readFileSync(0, 'utf8'))
+const values = [
+  dto.maximum.contractAmount,
+  dto.maximum.confirmedDepositAmount,
+  dto.maximumTransfer.transferAmount,
+  dto.normal.contractAmount,
+  dto.normal.transfers.find((item) => item.id === 'money-paid').transferAmount,
+  dto.empty.contractAmount,
+  dto.empty.confirmedDepositAmount
+]
+process.stdout.write(JSON.stringify(values.map(formatPresignMoney)))
+"""
+    formatted = subprocess.run(
+        ['node', '--input-type=module', '-e', script, presenter_url],
+        input=serialized,
+        text=True,
+        capture_output=True,
+        check=True,
+    )
+    assert json.loads(formatted.stdout) == [
+        '¥9,999,999,999,999,999.99',
+        '¥9,999,999,999,999,999.99',
+        '¥9,999,999,999,999,999.99',
+        '¥1,234.50',
+        '¥200.25',
+        '-',
+        '¥0.00',
+    ]