# API v1 Customer Address API ## Scope 客户地址 API 用于维护客户的一对多地址资料。地址归属 `basic_info.Customer`,并通过 `merchant` 做商户隔离。 所有接口需要登录。普通员工只能访问自己可见客户的地址;拥有 `basic_info.view_all_customers` 权限的员工可以访问本商户全部客户地址;superuser 可以访问全部数据。 ## Data Model | Field | Type | Description | | --- | --- | --- | | `id` | integer | 客户地址 ID | | `merchant` | integer | 所属商户 ID,创建时由后端根据客户自动写入 | | `merchant_name` | string | 所属商户名称,只读 | | `customer` | integer | 客户 ID | | `customer_name` | string | 客户名称,只读 | | `address` | string | 地址 | | `contact_name` | string | 联系人,可为空字符串 | | `contact_phone` | string | 联系电话,可为空字符串 | | `area` | string | 地区,可为空字符串 | | `coordinates` | string/null | 经纬度字符串,格式由调用方决定 | | `is_default` | boolean | 是否默认地址 | | `remark` | string | 备注,可为空字符串 | | `extra` | object/null | 扩展 JSON | | `created_by` | integer/null | 创建员工 ID,只读 | | `created_by_name` | string/null | 创建员工姓名,只读 | | `deleted_at` | datetime/null | 软删除时间,只读 | | `deleted_by` | integer/null | 删除员工 ID,只读 | | `deleted_by_name` | string/null | 删除员工姓名,只读 | | `created_at` | datetime | 创建时间,只读 | | `updated_at` | datetime | 更新时间,只读 | ## Endpoints | Method | Path | Description | | --- | --- | --- | | GET | `/api/v1/customer-addresses/` | 查询客户地址列表 | | POST | `/api/v1/customer-addresses/` | 创建客户地址 | | GET | `/api/v1/customer-addresses/{id}/` | 查询客户地址详情 | | PUT | `/api/v1/customer-addresses/{id}/` | 完整更新客户地址 | | PATCH | `/api/v1/customer-addresses/{id}/` | 部分更新客户地址 | | DELETE | `/api/v1/customer-addresses/{id}/` | 软删除客户地址 | | POST | `/api/v1/customer-address/{id}/set-default/` | 将客户地址设为默认地址 | ## Visibility Rules 普通员工可访问的客户地址必须同时满足: - 地址所属商户等于当前员工商户。 - 地址所属客户对当前员工可见。 客户可见性规则: - `customer.created_by == 当前员工` - 或当前员工在 `customer.visible_employees` 中。 权限例外: - `basic_info.view_all_customers`:可访问本商户全部客户地址。 - superuser:可访问全部客户地址。 创建地址时,`customer` 也必须在当前用户可访问范围内。`merchant` 不由前端传入,后端使用客户所属商户自动写入。 ## Query Parameters `GET /api/v1/customer-addresses/` 支持以下查询参数: | Parameter | Description | | --- | --- | | `customer` | 按客户 ID 精确过滤 | | `customer_name` | 按客户名称模糊过滤 | | `address` | 按地址模糊过滤 | | `contact_name` | 按联系人模糊过滤 | | `contact_phone` | 按联系电话模糊过滤 | | `area` | 按地区模糊过滤 | | `is_default` | 按默认地址过滤,支持 `true/false/1/0/yes/no` | | `include_deleted` | 是否包含软删除记录,支持 `true/false/1/0/yes/no` | | `only_deleted` | 是否只查询软删除记录,支持 `true/false/1/0/yes/no` | | `search` | 对客户名称、地址、联系人、电话、地区、备注做模糊搜索 | | `ordering` | 排序字段,支持 `id`、`created_at`、`updated_at`、`is_default` 及其 `-` 倒序形式 | | `limit` | 分页数量 | | `offset` | 分页偏移 | 默认行为: - 不传 `include_deleted` 和 `only_deleted` 时,只返回未删除地址。 - `only_deleted=true` 优先于 `include_deleted=true`。 ## Create `POST /api/v1/customer-addresses/` Request body: ```json { "customer": 3, "address": "杭州市测试路 1 号", "contact_name": "张三", "contact_phone": "13800138000", "area": "杭州", "coordinates": "120.1,30.2", "is_default": true, "remark": "默认收货地址", "extra": { "source": "manual" } } ``` Success response: HTTP 201 ```json { "id": 10, "merchant": 1, "merchant_name": "测试印花厂", "customer": 3, "customer_name": "客户A", "address": "杭州市测试路 1 号", "contact_name": "张三", "contact_phone": "13800138000", "area": "杭州", "coordinates": "120.1,30.2", "is_default": true, "remark": "默认收货地址", "extra": { "source": "manual" }, "created_by": 5, "created_by_name": "业务员A", "deleted_at": null, "deleted_by": null, "deleted_by_name": null, "created_at": "2026-07-07T12:00:00Z", "updated_at": "2026-07-07T12:00:00Z" } ``` ## Update `PATCH /api/v1/customer-addresses/{id}/` Example: ```json { "address": "杭州市更新路 2 号", "contact_phone": "13900139000", "coordinates": null } ``` Notes: - 不允许修改 `customer`。 - 不允许修改 `merchant`。 - `created_by`、`deleted_at`、`deleted_by` 为只读字段。 ## Default Address 同一客户最多一个未删除默认地址。 以下操作会自动取消同一客户其他未删除地址的默认状态: - 创建地址时传 `is_default=true`。 - 更新地址时传 `is_default=true`。 - 调用 `set-default` 接口。 `POST /api/v1/customer-address/{id}/set-default/` Success response: HTTP 200,返回更新后的地址对象。 如果地址已经软删除,返回 HTTP 400。 ## Soft Delete `DELETE /api/v1/customer-addresses/{id}/` 删除是软删除,不会物理删除记录。后端会写入: - `deleted_at` - `deleted_by` - `is_default=false` Success response: HTTP 204。 查询软删除记录: ```http GET /api/v1/customer-addresses/?include_deleted=true GET /api/v1/customer-addresses/?only_deleted=true ``` ## Error Cases | Condition | Status | Notes | | --- | --- | --- | | 未登录 | 401 | 全部接口需要登录 | | 查询不可见地址详情 | 404 | 不暴露不可见数据存在性 | | 给不可见客户创建地址 | 400 | `customer` 字段校验失败 | | 修改 `customer` | 400 | 不允许地址跨客户移动 | | 已删除地址设为默认 | 400 | 通过 `set-default` 接口返回校验错误 | ## Test Coverage Dedicated tests: ```bash docker compose exec -T -e DB_HOST=postgres -e DB_PORT=5432 web uv run python manage.py test api_v1.test_customer_address_api --keepdb --noinput ``` Covered behavior: - 商户隔离和客户可见性。 - `visible_employees` 授权后可见。 - `basic_info.view_all_customers` 可访问本商户全部客户地址。 - 创建地址时自动写入 `merchant` 和 `created_by`。 - 不可见客户创建地址失败。 - 默认地址互斥。 - `set-default` 接口互斥设置默认地址。 - 软删除和 `include_deleted` / `only_deleted` 查询。 - 常用过滤参数。