test(planning): cover C2 fix workflows
This commit is contained in:
parent
ff2ed7542d
commit
ddef82ae8e
1 changed files with 117 additions and 1 deletions
|
|
@ -10,10 +10,14 @@ from app_new.core.models.planning import (
|
||||||
Meter, MeterReading, MeterReadingOccurrence, MeterReadingRoundOccurrence,
|
Meter, MeterReading, MeterReadingOccurrence, MeterReadingRoundOccurrence,
|
||||||
MeterReadingSchedule, WorkSchedule,
|
MeterReadingSchedule, WorkSchedule,
|
||||||
)
|
)
|
||||||
|
from app_new.core.models.user import User
|
||||||
|
from app_new.core.models.rbac import Role, UserRole
|
||||||
from app_new.core.services.meter_reading_planning import (
|
from app_new.core.services.meter_reading_planning import (
|
||||||
CANCELLED, TREATED_READING, TREATED_WITHOUT_READING, add_round_member,
|
CANCELLED, TREATED_READING, TREATED_WITHOUT_READING, add_round_member,
|
||||||
create_or_update_schedule, create_round, generate_occurrences,
|
create_or_update_schedule, create_round,
|
||||||
|
generate_occurrences,
|
||||||
operational_date_for, record_occurrence_reading, record_occurrence_without_reading,
|
operational_date_for, record_occurrence_reading, record_occurrence_without_reading,
|
||||||
|
MeterDomainError,
|
||||||
)
|
)
|
||||||
from app_new.core.services.meter_service import create_meter, replace_meter
|
from app_new.core.services.meter_service import create_meter, replace_meter
|
||||||
|
|
||||||
|
|
@ -33,6 +37,18 @@ def _schedule(meter, admin_user, frequency, reference, **kwargs):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _test_user(suffix):
|
||||||
|
username = f"TEST_UI_C2_FIX_{suffix}"
|
||||||
|
user = User(username=username, email=f"{username.lower()}@gmao.local", full_name=username, role="technicien", is_active=True)
|
||||||
|
user.set_password("test-c2-fix-password")
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.flush()
|
||||||
|
role = Role.query.filter_by(slug="technicien", is_active=True).first()
|
||||||
|
db.session.add(UserRole(user_id=user.id, role_id=role.id))
|
||||||
|
db.session.flush()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def test_c2_frequencies_and_generation_are_idempotent(app, admin_user):
|
def test_c2_frequencies_and_generation_are_idempotent(app, admin_user):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
frequencies = {
|
frequencies = {
|
||||||
|
|
@ -181,6 +197,106 @@ def test_c2_round_order_progress_and_resume(app, admin_user, authenticated_clien
|
||||||
)
|
)
|
||||||
db.session.refresh(round_occurrence)
|
db.session.refresh(round_occurrence)
|
||||||
assert round_occurrence.status == "EN COURS"
|
assert round_occurrence.status == "EN COURS"
|
||||||
|
record_occurrence_reading(
|
||||||
|
occurrence_id=round_occurrence.occurrences[1].id, value=10, user_id=admin_user["id"]
|
||||||
|
)
|
||||||
|
db.session.refresh(round_occurrence)
|
||||||
|
assert round_occurrence.status == "TERMINÉE"
|
||||||
|
db.session.expire_all()
|
||||||
|
response = authenticated_client.get(f"/planning/meter-rounds/{round_.id}?date=2026-10-01")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b"2 / 2" in response.data
|
||||||
|
assert response.data.index(b"ROUND_1") < response.data.index(b"ROUND_2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_c2_fix_direct_round_generates_future_target_on_operational_date(app, admin_user, authenticated_client):
|
||||||
|
with app.app_context():
|
||||||
|
meter = _meter("DIRECT_OPERATIONAL_DATE")
|
||||||
|
schedule = create_or_update_schedule(
|
||||||
|
meter=meter, frequency="FIXED_ANNUAL_DATE", reference_date=date(2023, 10, 1),
|
||||||
|
fixed_month=10, fixed_day=1, assigned_to_id=None,
|
||||||
|
)
|
||||||
|
db.session.add(WorkSchedule(user_id=admin_user["id"], day_of_week=4, start_time=time(8), end_time=time(17), is_active=True))
|
||||||
|
round_ = create_round(name="TEST_UI_C2_FIX_DIRECT_ROUND", default_assigned_to_id=admin_user["id"])
|
||||||
|
add_round_member(round_=round_, schedule=schedule, position=1)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Aucun appel préalable à Ma journée ni génération couvrant le 01/10.
|
||||||
|
assert MeterReadingOccurrence.query.filter_by(schedule_id=schedule.id).count() == 0
|
||||||
|
response = authenticated_client.get(f"/planning/meter-rounds/{round_.id}?date=2023-09-29")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b"TEST_UI_C2_DIRECT_OPERATIONAL_DATE" in response.data
|
||||||
|
occurrence = MeterReadingOccurrence.query.filter_by(schedule_id=schedule.id).one()
|
||||||
|
assert occurrence.target_date == date(2023, 10, 1)
|
||||||
|
assert occurrence.operational_date == date(2023, 9, 29)
|
||||||
|
assert occurrence.round_occurrence.round_id == round_.id
|
||||||
|
assert occurrence.assigned_to_id == admin_user["id"]
|
||||||
|
second_response = authenticated_client.get(f"/planning/meter-rounds/{round_.id}?date=2023-09-29")
|
||||||
|
assert second_response.status_code == 200
|
||||||
|
assert MeterReadingOccurrence.query.filter_by(schedule_id=schedule.id).count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_c2_fix_schedule_assignment_precedes_round_default(app, admin_user):
|
||||||
|
with app.app_context():
|
||||||
|
individual = _test_user("INDIVIDUAL")
|
||||||
|
meter = _meter("ASSIGNMENT_PRIORITY")
|
||||||
|
schedule = create_or_update_schedule(
|
||||||
|
meter=meter, frequency="MONTHLY", reference_date=date(2026, 10, 1),
|
||||||
|
assigned_to_id=individual.id,
|
||||||
|
)
|
||||||
|
round_ = create_round(name="TEST_UI_C2_FIX_PRIORITY", default_assigned_to_id=admin_user["id"])
|
||||||
|
add_round_member(round_=round_, schedule=schedule, position=1)
|
||||||
|
db.session.commit()
|
||||||
|
generate_occurrences(start_date=date(2026, 10, 1), end_date=date(2026, 10, 1), schedule_ids=[schedule.id])
|
||||||
|
occurrence = MeterReadingOccurrence.query.filter_by(schedule_id=schedule.id).one()
|
||||||
|
assert occurrence.assigned_to_id == individual.id
|
||||||
|
|
||||||
|
|
||||||
|
def test_c2_fix_double_processing_is_rejected_without_duplicate_reading(app, admin_user):
|
||||||
|
with app.app_context():
|
||||||
|
meter = _meter("DOUBLE_PROCESSING")
|
||||||
|
schedule = _schedule(meter, admin_user, "MONTHLY", date(2026, 10, 1))
|
||||||
|
generate_occurrences(start_date=date(2026, 10, 1), end_date=date(2026, 10, 1), schedule_ids=[schedule.id])
|
||||||
|
occurrence = MeterReadingOccurrence.query.filter_by(schedule_id=schedule.id).one()
|
||||||
|
record_occurrence_reading(occurrence_id=occurrence.id, value=5, user_id=admin_user["id"])
|
||||||
|
with pytest.raises(MeterDomainError, match="déjà traitée ou annulée"):
|
||||||
|
record_occurrence_reading(occurrence_id=occurrence.id, value=6, user_id=admin_user["id"])
|
||||||
|
assert MeterReading.query.filter_by(meter_id=meter.id).count() == 1
|
||||||
|
|
||||||
|
other = _meter("DOUBLE_WITHOUT")
|
||||||
|
other_schedule = _schedule(other, admin_user, "MONTHLY", date(2026, 10, 1))
|
||||||
|
generate_occurrences(start_date=date(2026, 10, 1), end_date=date(2026, 10, 1), schedule_ids=[other_schedule.id])
|
||||||
|
other_occurrence = MeterReadingOccurrence.query.filter_by(schedule_id=other_schedule.id).one()
|
||||||
|
record_occurrence_without_reading(occurrence_id=other_occurrence.id, reason="INACCESSIBLE", user_id=admin_user["id"])
|
||||||
|
with pytest.raises(MeterDomainError, match="déjà traitée ou annulée"):
|
||||||
|
record_occurrence_reading(occurrence_id=other_occurrence.id, value=9, user_id=admin_user["id"])
|
||||||
|
assert MeterReading.query.filter_by(meter_id=other.id).count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_c2_fix_user_isolation_http(app):
|
||||||
|
with app.app_context():
|
||||||
|
user_a = _test_user("USER_A")
|
||||||
|
user_b = _test_user("USER_B")
|
||||||
|
meter = _meter("USER_ISOLATION")
|
||||||
|
schedule = create_or_update_schedule(
|
||||||
|
meter=meter, frequency="MONTHLY", reference_date=date(2026, 10, 1), assigned_to_id=user_a.id,
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
generate_occurrences(start_date=date(2026, 10, 1), end_date=date(2026, 10, 1), schedule_ids=[schedule.id])
|
||||||
|
occurrence = MeterReadingOccurrence.query.filter_by(schedule_id=schedule.id).one()
|
||||||
|
client = app.test_client()
|
||||||
|
response = client.post('/auth/login', data={'username': user_b.username, 'password': 'test-c2-fix-password'})
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert client.get(f"/planning/meter-occurrences/{occurrence.id}").status_code == 403
|
||||||
|
assert client.post(f"/planning/meter-occurrences/{occurrence.id}/reading", data={'value': '12'}).status_code == 302
|
||||||
|
db.session.refresh(occurrence)
|
||||||
|
assert occurrence.status == "À FAIRE"
|
||||||
|
client.get('/auth/logout')
|
||||||
|
response = client.post('/auth/login', data={'username': user_a.username, 'password': 'test-c2-fix-password'})
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert client.post(f"/planning/meter-occurrences/{occurrence.id}/without-reading", data={'reason': 'READING_UNKNOWN'}).status_code == 302
|
||||||
|
db.session.refresh(occurrence)
|
||||||
|
assert occurrence.status == TREATED_WITHOUT_READING
|
||||||
|
|
||||||
|
|
||||||
def test_c2_permissions_keep_configuration_admin_only(app, client):
|
def test_c2_permissions_keep_configuration_admin_only(app, client):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue