feat: 新增现场日历并将四类日历的起止地改为选填,以支持现场派车记录。

This commit is contained in:
wuzilong committed 2026-10-30 11:58:48 +08:00
1 parent 0453e90000
commit cd99b040aa
15 files changed
+172 -31

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@ class PlDispatchCalendar(Base):
quantity = Column(String(16), nullable=True, comment="数量文本(吨),最多两位小数,不参与汇总")
calendar_kind = Column(
String(16), nullable=False, default="acid", server_default="acid",
comment="日历类型:acid酸派车 alkali碱派车 other其他",
comment="日历类型:acid酸派车 alkali碱派车 other其他 site现场",
)
tenant_no = Column(String(64), nullable=False, comment="租户编号")
order_num = Column(Integer, nullable=True, comment="排序号")
+4 -4
View File
@@ -12,8 +12,8 @@ class CreateBody(BaseModel):
dispatch_date: date = Field(..., alias="dispatchDate", description="派车日期,范围 1900-01-01 至 2100-12-31")
vehicle_id: str = Field(..., alias="vehicleId", min_length=1, max_length=64)
product_id: str = Field(..., alias="productId", min_length=1, max_length=64)
origin: str = Field(..., min_length=1, max_length=255)
destination: str = Field(..., min_length=1, max_length=255)
origin: str = Field(default="", max_length=255)
destination: str = Field(default="", max_length=255)
quantity: Optional[str] = Field(default=None, max_length=16, description="数量文本,单位吨,最多两位小数;不参与汇总")
calendar_kind: str = Field(default="acid", alias="calendarKind", min_length=1, max_length=16)
remark: Optional[str] = Field(default=None, max_length=500)
@@ -51,8 +51,8 @@ class CreateBody(BaseModel):
@field_validator("calendar_kind")
@classmethod
def validate_calendar_kind(cls, value: str) -> str:
if value not in ("acid", "alkali", "other"):
raise ValueError("日历类型须为 acid、alkali 或 other")
if value not in ("acid", "alkali", "other", "site"):
raise ValueError("日历类型须为 acid、alkali、other 或 site")
return value
@@ -14,13 +14,13 @@ from app.models.pl_models import PlVehicle
from app.schemas.pl.dispatch_calendar import CreateBody, UpdateBody
from app.services.pl_data_scope import apply_pl_scope
CALENDAR_KINDS = ("acid", "alkali", "other")
CALENDAR_KINDS = ("acid", "alkali", "other", "site")
def _calendar_kind(kind: Optional[str]) -> str:
value = kind or "acid"
if value not in CALENDAR_KINDS:
raise HTTPException(status_code=422, detail="日历类型须为 acid、alkali 或 other")
raise HTTPException(status_code=422, detail="日历类型须为 acid、alkali、other 或 site")
return value
+49 -1
View File
@@ -166,7 +166,7 @@ def test_invalid_cross_tenant_and_deleted_relations_rejected(db, field, value):
@pytest.mark.parametrize('bad', [
{'dispatchDate': '2026-02-30'}, {'dispatchDate': '1899-12-31'}, {'dispatchDate': '2101-01-01'},
{'dispatchDate': 1792972800}, {'dispatchDate': '2026-10-26T00:00:00'}, {'origin': ' '}, {'destination': 'x' * 256},
{'dispatchDate': 1792972800}, {'dispatchDate': '2026-10-26T00:00:00'}, {'origin': 'x' * 256}, {'destination': 'x' * 256},
{'vehicleId': ''}, {'productId': ''}, {'remark': 'x' * 501}, {'calendarKind': 'salt'}, {'tenantNo': 't2'},
{'licensePlate': '伪造'}, {'productName': '伪造'}, {'createBy': 'other'},
])
@@ -340,3 +340,51 @@ def test_calendar_openapi_schema_can_be_generated():
for schema in ('CreateBody', 'UpdateBody'):
field = document['components']['schemas'][schema]['properties']['dispatchDate']
assert field['type'] == 'string' and field['format'] == 'date'
def test_site_calendar_crud_isolation_and_kind_protection(db):
model, _, update, svc = modules()
existing = {kind: create(db, calendarKind=kind) for kind in ('acid', 'alkali', 'other')}
site = create(db, calendarKind='site', origin='现场', quantity='32.50')
original_id, original_created = site.id, site.create_time
page = svc.list_records(db, 't1', user(), month='2026-10', calendar_kind='site')
assert page['total'] == 1 and page['list'][0]['id'] == site.id
assert page['list'][0]['calendarKind'] == 'site'
assert svc.list_records(db, 't2', user(tenant='t2'), month='2026-10', calendar_kind='site')['total'] == 0
assert svc.list_records(db, 't1', user(), month='2026-10', calendar_kind='site', vehicle_id='v2')['total'] == 0
for kind, row in existing.items():
assert svc.list_records(db, 't1', user(), month='2026-10', calendar_kind=kind)['list'][0]['id'] == row.id
with pytest.raises(HTTPException) as moved:
svc.update_record(db, 't1', user(), site.id, update(**payload(calendarKind=kind)))
assert moved.value.status_code == 422
kept = svc.update_record(db, 't1', user(), site.id, update(**payload(calendarKind='site', destination='现场仓库')))
assert kept.id == original_id and kept.create_time == original_created
assert kept.calendar_kind == 'site' and kept.destination == '现场仓库'
assert svc.delete_record(db, 't1', user(), site.id) == 1
assert db.get(model, original_id).del_flag == 1
assert svc.list_records(db, 't1', user(), month='2026-10', calendar_kind='site')['total'] == 0
@pytest.mark.parametrize('kind', ['acid', 'alkali', 'other', 'site'])
@pytest.mark.parametrize('addresses', [
{'origin': '', 'destination': '重庆'},
{'origin': '宜昌', 'destination': ''},
{'origin': ' ', 'destination': ' '},
])
def test_calendar_addresses_optional_on_create_and_update(db, kind, addresses):
_, schema, update, svc = modules()
created = create(db, calendarKind=kind, **addresses)
result = svc._row_to_dict(created)
assert result['origin'] == addresses['origin'].strip()
assert result['destination'] == addresses['destination'].strip()
existing = create(db, calendarKind=kind)
updated = svc.update_record(db, 't1', user(), existing.id,
update(**payload(calendarKind=kind, **addresses)))
assert updated.id == existing.id
assert updated.origin == addresses['origin'].strip()
assert updated.destination == addresses['destination'].strip()
omitted = payload(calendarKind=kind)
omitted.pop('origin')
omitted.pop('destination')
body = schema(**omitted)
assert body.origin == body.destination == ''