Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions backend/app/api/customers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from sqlalchemy.orm import Session, selectinload

from app.db.session import get_db
from app.maps import MapServices
from app.maps.factory import get_map_services
from app.models.customer import Cat, Customer
from app.models.order import Order, OrderCat
from app.models.payment import Payment
Expand All @@ -22,11 +24,18 @@
CustomerSummary,
CustomerUpdate,
)
from app.schemas.location import CustomerLocationRestore, CustomerLocationUpdate, LocationUpdateRead
from app.services.customers import build_customer
from app.services.manual_locations import (
check_customer_location_concurrency,
update_customer_location,
verified_geocode,
)


router = APIRouter(prefix="/api/admin/customers", tags=["admin-customers"])
DatabaseSession = Annotated[Session, Depends(get_db)]
MapServicesDependency = Annotated[MapServices, Depends(get_map_services)]
GEOCODE_ADDRESS_FIELDS = {"community", "address", "building", "unit", "room"}


Expand Down Expand Up @@ -174,6 +183,59 @@ def get_customer(customer_id: int, session: DatabaseSession) -> CustomerDetail:
return _customer_detail(session, _load_customer(session, customer_id))


@router.patch("/{customer_id}/location", response_model=LocationUpdateRead)
def patch_customer_location(
customer_id: int,
payload: CustomerLocationUpdate,
session: DatabaseSession,
) -> LocationUpdateRead:
result = update_customer_location(
session,
customer_id=customer_id,
source_order_id=payload.source_order_id,
service_date=payload.service_date,
expected_customer_updated_at=payload.expected_customer_updated_at,
expected_day_revision=payload.expected_day_revision,
latitude=payload.latitude,
longitude=payload.longitude,
)
session.commit()
return result


@router.post("/{customer_id}/location/restore-auto", response_model=LocationUpdateRead)
def restore_customer_location(
customer_id: int,
payload: CustomerLocationRestore,
session: DatabaseSession,
services: MapServicesDependency,
) -> LocationUpdateRead:
_, order, _ = check_customer_location_concurrency(
session,
customer_id=customer_id,
source_order_id=payload.source_order_id,
service_date=payload.service_date,
expected_customer_updated_at=payload.expected_customer_updated_at,
expected_day_revision=payload.expected_day_revision,
)
result, provider_name = verified_geocode(order, services)
session.expire_all()
updated = update_customer_location(
session,
customer_id=customer_id,
source_order_id=payload.source_order_id,
service_date=payload.service_date,
expected_customer_updated_at=payload.expected_customer_updated_at,
expected_day_revision=payload.expected_day_revision,
latitude=result.point.latitude,
longitude=result.point.longitude,
automatic_result=result,
provider_name=provider_name,
)
session.commit()
return updated


@router.patch("/{customer_id}", response_model=CustomerDetail)
def update_customer(
customer_id: int,
Expand Down
101 changes: 94 additions & 7 deletions backend/app/api/intake.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from app.db.session import get_db
from app.models.enums import FormSubmissionStatus
from app.models.intake import CustomerFormSubmission, CustomerFormToken
from app.models.system import SystemFlag
from app.schemas.intake import (
IntakeClaimCommand,
IntakeClaimRead,
Expand All @@ -16,6 +17,7 @@
IntakeDecisionCommand,
IntakeDecisionRead,
IntakeDraftPayload,
IntakeListStateUpdate,
IntakeRedactionRead,
IntakeReviewDraftUpdate,
IntakeSubmissionDetail,
Expand Down Expand Up @@ -66,6 +68,7 @@
tags=["intake-relay-sync"],
)
DatabaseSession = Annotated[Session, Depends(get_db)]
REMOVED_FLAG_PREFIX = "intake-list-removed:"


def _token_options():
Expand All @@ -83,6 +86,49 @@ def _load_admin_token(session: Session, token_id: int) -> CustomerFormToken:
return token


def _removed_key(submission_uuid: str) -> str:
return f"{REMOVED_FLAG_PREFIX}{submission_uuid}"


def _decorate_removed_state(
session: Session,
detail: IntakeSubmissionDetail,
) -> IntakeSubmissionDetail:
flag = session.get(SystemFlag, _removed_key(detail.submission_uuid))
return detail.model_copy(update={"removed_at": flag.created_at if flag else None})


def _decorate_removed_list(
session: Session,
response: IntakeSubmissionList,
) -> IntakeSubmissionList:
keys = [_removed_key(item.submission_uuid) for item in response.items]
flags = {
flag.key: flag
for flag in (
session.scalars(select(SystemFlag).where(SystemFlag.key.in_(keys))).all()
if keys
else []
)
}
return response.model_copy(
update={
"items": [
item.model_copy(
update={
"removed_at": (
flags[_removed_key(item.submission_uuid)].created_at
if _removed_key(item.submission_uuid) in flags
else None
)
}
)
for item in response.items
]
}
)


def _submission_options():
return (
selectinload(CustomerFormSubmission.token),
Expand Down Expand Up @@ -235,9 +281,12 @@ def list_submissions(
offset: int = Query(default=0, ge=0),
) -> IntakeSubmissionList:
if remote_intake_enabled():
return hydrate_remote_list(
return _decorate_removed_list(
session,
RemoteIntakeClient().list_submissions(limit=limit, offset=offset),
hydrate_remote_list(
session,
RemoteIntakeClient().list_submissions(limit=limit, offset=offset),
),
)
status_filter = CustomerFormSubmission.status != FormSubmissionStatus.DRAFT
total = session.scalar(
Expand All @@ -254,9 +303,12 @@ def list_submissions(
.limit(limit)
.offset(offset)
).all()
return IntakeSubmissionList(
items=[to_submission_summary(item) for item in submissions],
total=total,
return _decorate_removed_list(
session,
IntakeSubmissionList(
items=[to_submission_summary(item) for item in submissions],
total=total,
),
)


Expand All @@ -273,11 +325,46 @@ def get_submission(
session: DatabaseSession,
) -> IntakeSubmissionDetail:
if remote_intake_enabled():
return hydrate_remote_detail(
return _decorate_removed_state(
session,
hydrate_remote_detail(
session,
RemoteIntakeClient().get_submission(submission_id),
),
)
return _decorate_removed_state(
session,
to_submission_detail(_load_submission(session, submission_id)),
)


@admin_router.patch(
"/submissions/{submission_id}/list-state",
response_model=IntakeSubmissionDetail,
)
def update_submission_list_state(
submission_id: int,
payload: IntakeListStateUpdate,
session: DatabaseSession,
) -> IntakeSubmissionDetail:
detail = (
hydrate_remote_detail(
session,
RemoteIntakeClient().get_submission(submission_id),
)
return to_submission_detail(_load_submission(session, submission_id))
if remote_intake_enabled()
else to_submission_detail(_load_submission(session, submission_id))
)
if detail.revision != payload.expected_revision:
raise HTTPException(status_code=409, detail="提交记录已变化,请刷新后重试")
key = _removed_key(detail.submission_uuid)
flag = session.get(SystemFlag, key)
if payload.removed and flag is None:
session.add(SystemFlag(key=key, payload={"removed": True}))
elif not payload.removed and flag is not None:
session.delete(flag)
session.commit()
return _decorate_removed_state(session, detail)


@admin_router.put(
Expand Down
56 changes: 56 additions & 0 deletions backend/app/api/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@
OrderWrite,
OrderServiceContact,
)
from app.schemas.location import LocationUpdateRead, OrderLocationRestore, OrderLocationUpdate
from app.services.order_customers import resolve_order_customer
from app.services.order_locations import clear_order_location, geocode_order
from app.services.manual_locations import (
check_order_location_concurrency,
update_order_location,
verified_geocode,
)
from app.services.orders import (
DEFAULT_BASE_PRICE,
EXTRA_CAT_UNIT_PRICE,
Expand Down Expand Up @@ -553,6 +559,56 @@ def retry_order_geocode(
return _order_detail(_load_order(session, order_id))


@router.patch("/{order_id}/location", response_model=LocationUpdateRead)
def patch_order_location(
order_id: int,
payload: OrderLocationUpdate,
session: DatabaseSession,
) -> LocationUpdateRead:
result = update_order_location(
session,
order_id=order_id,
service_date=payload.service_date,
expected_order_updated_at=payload.expected_order_updated_at,
expected_day_revision=payload.expected_day_revision,
latitude=payload.latitude,
longitude=payload.longitude,
)
session.commit()
return result


@router.post("/{order_id}/location/restore-auto", response_model=LocationUpdateRead)
def restore_order_location(
order_id: int,
payload: OrderLocationRestore,
session: DatabaseSession,
services: MapServicesDependency,
) -> LocationUpdateRead:
_, order = check_order_location_concurrency(
session,
order_id=order_id,
service_date=payload.service_date,
expected_order_updated_at=payload.expected_order_updated_at,
expected_day_revision=payload.expected_day_revision,
)
result, provider_name = verified_geocode(order, services)
session.expire_all()
updated = update_order_location(
session,
order_id=order_id,
service_date=payload.service_date,
expected_order_updated_at=payload.expected_order_updated_at,
expected_day_revision=payload.expected_day_revision,
latitude=result.point.latitude,
longitude=result.point.longitude,
automatic_result=result,
provider_name=provider_name,
)
session.commit()
return updated


@router.put("/{order_id}", response_model=OrderDetail)
def update_order(
order_id: int,
Expand Down
Loading