diff --git a/backend/app/models/chargepoint.py b/backend/app/models/chargepoint.py index e24e180..c41d0d3 100644 --- a/backend/app/models/chargepoint.py +++ b/backend/app/models/chargepoint.py @@ -25,3 +25,4 @@ class ChargePoint(Base): connectors = relationship("Connector", cascade="delete, delete-orphan") transactions = relationship("Transaction", cascade="delete, delete-orphan") variables = relationship("ChargepointVariable", cascade="delete, delete-orphan") + firmware_updates = relationship("FirmwareUpdate", cascade="delete, delete-orphan") diff --git a/backend/app/models/firmware_update.py b/backend/app/models/firmware_update.py index 9e4fc4b..5d0d9f7 100644 --- a/backend/app/models/firmware_update.py +++ b/backend/app/models/firmware_update.py @@ -1,5 +1,6 @@ import uuid from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String, Text, Uuid +from sqlalchemy.orm import relationship from app.database import Base from app.schemas.firmware_update import FirmwareUpdateStatus @@ -20,3 +21,4 @@ class FirmwareUpdate(Base): signature = Column(String, nullable=True) chargepoint_id = Column(Uuid, ForeignKey("chargepoints.id"), index=True) + chargepoint = relationship("ChargePoint", back_populates="firmware_updates") diff --git a/backend/app/models/meter_value.py b/backend/app/models/meter_value.py index e2528ed..5adf6a1 100644 --- a/backend/app/models/meter_value.py +++ b/backend/app/models/meter_value.py @@ -1,5 +1,6 @@ import uuid from sqlalchemy import Uuid, Column, DateTime, Enum, Float, ForeignKey, String +from sqlalchemy.orm import relationship from app.database import Base from app.schemas.meter_value import Measurand, PhaseType @@ -14,4 +15,5 @@ class MeterValue(Base): unit = Column(String, nullable=True) value = Column(Float) - transaction_id = Column(String, ForeignKey("transactions.id"), index=True) \ No newline at end of file + transaction_id = Column(String, ForeignKey("transactions.id"), index=True) + transaction = relationship("Transaction", back_populates="meter_values") \ No newline at end of file diff --git a/backend/app/models/session.py b/backend/app/models/session.py index cd0c5ba..d9c0ce2 100644 --- a/backend/app/models/session.py +++ b/backend/app/models/session.py @@ -1,5 +1,6 @@ import uuid from sqlalchemy import Column, DateTime, ForeignKey, String, Uuid +from sqlalchemy.orm import relationship from app.database import Base @@ -11,4 +12,5 @@ class Session(Base): refresh_token = Column(String, nullable=False, unique=True, index=True) last_used = Column(DateTime(timezone=True)) - user_id = Column(Uuid, ForeignKey("users.id"), nullable=False, index=True) \ No newline at end of file + user_id = Column(Uuid, ForeignKey("users.id"), nullable=False, index=True) + user = relationship("User", back_populates="sessions") \ No newline at end of file diff --git a/backend/app/models/transaction.py b/backend/app/models/transaction.py index a918430..a9d4452 100644 --- a/backend/app/models/transaction.py +++ b/backend/app/models/transaction.py @@ -1,4 +1,5 @@ from sqlalchemy import String, Uuid, Column, DateTime, Enum, Numeric, ForeignKey +from sqlalchemy.orm import relationship from app.schemas.transaction import TransactionEventTriggerReason, TransactionStatus from app.database import Base @@ -11,9 +12,14 @@ class Transaction(Base): started_at = Column(DateTime, index=True) ended_at = Column(DateTime, nullable=True, index=True) meter_start = Column(Numeric(10,2)) - meter_end = Column(Numeric(10,2), nullable=True) + meter_end = Column(Numeric(10,2)) end_reason = Column(Enum(TransactionEventTriggerReason), nullable=True) price = Column(Numeric(10,2)) user_id = Column(Uuid, ForeignKey("users.id"), nullable=True, index=True) + user = relationship("User", back_populates="transactions") + chargepoint_id = Column(Uuid, ForeignKey("chargepoints.id"), index=True) + chargepoint = relationship("ChargePoint", back_populates="transactions") + + meter_values = relationship("MeterValue", cascade="delete, delete-orphan") diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 75cf5da..bfdefb9 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -17,3 +17,4 @@ class User(Base): id_tokens = relationship("IdToken", back_populates="owner", cascade="delete, delete-orphan") transactions = relationship("Transaction", cascade="delete, delete-orphan") + sessions = relationship("Session", cascade="delete, delete-orphan") diff --git a/backend/app/schemas/auth_token.py b/backend/app/schemas/auth_token.py index bb9ee60..9ae5534 100644 --- a/backend/app/schemas/auth_token.py +++ b/backend/app/schemas/auth_token.py @@ -3,6 +3,7 @@ from datetime import datetime from pydantic import BaseModel from app.schemas.user import Role +from app.util.encoders import force_utc_datetime @dataclass @@ -19,3 +20,6 @@ class TokenResponse(BaseModel): access_token: str refresh_token: str not_after: datetime + + class Config: + json_encoders = {datetime: force_utc_datetime} diff --git a/backend/app/schemas/chargepoint.py b/backend/app/schemas/chargepoint.py index 2493315..878b20a 100644 --- a/backend/app/schemas/chargepoint.py +++ b/backend/app/schemas/chargepoint.py @@ -8,7 +8,7 @@ from app.schemas.connector import Connector from ocpp.v201.enums import ResetEnumType, ResetStatusEnumType -from app.util.encoders import decimal_encoder +from app.util.encoders import decimal_encoder, force_utc_datetime class ChargePointBase(BaseModel): identity: str @@ -32,6 +32,15 @@ class ChargePoint(ChargePointBase): firmware_version: str | None connectors: list[Connector] = [] + class Config: + from_attributes = True + json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime} + +class ChargePointThumb(BaseModel): + id: UUID + identity: str + price: Decimal + class Config: from_attributes = True json_encoders = {Decimal: decimal_encoder} diff --git a/backend/app/schemas/meter_value.py b/backend/app/schemas/meter_value.py index 99f6945..abc08f1 100644 --- a/backend/app/schemas/meter_value.py +++ b/backend/app/schemas/meter_value.py @@ -5,7 +5,7 @@ from typing import Optional from uuid import UUID from pydantic import BaseModel -from app.util.encoders import decimal_encoder +from app.util.encoders import decimal_encoder, force_utc_datetime class PhaseType(enum.Enum): L1 = "L1" @@ -57,4 +57,4 @@ class MeterValue(BaseModel): class Config: from_attributes = True - json_encoders = {Decimal: decimal_encoder} + json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime} diff --git a/backend/app/schemas/session.py b/backend/app/schemas/session.py index 07b98a1..d9bbf35 100644 --- a/backend/app/schemas/session.py +++ b/backend/app/schemas/session.py @@ -2,6 +2,8 @@ from datetime import datetime from uuid import UUID from pydantic import BaseModel +from app.util.encoders import force_utc_datetime + class Session(BaseModel): id: UUID @@ -9,4 +11,5 @@ class Session(BaseModel): last_used: datetime class Config: - from_attributes = True \ No newline at end of file + from_attributes = True + json_encoders = {datetime: force_utc_datetime} \ No newline at end of file diff --git a/backend/app/schemas/transaction.py b/backend/app/schemas/transaction.py index 0188f91..ca242ee 100644 --- a/backend/app/schemas/transaction.py +++ b/backend/app/schemas/transaction.py @@ -1,11 +1,12 @@ +import enum from datetime import datetime from decimal import Decimal -import enum from typing import Optional -from uuid import UUID from pydantic import BaseModel -from app.util.encoders import decimal_encoder +from app.schemas.chargepoint import ChargePointThumb +from app.schemas.user import UserThumb +from app.util.encoders import decimal_encoder, force_utc_datetime class TransactionStatus(enum.Enum): ONGOING = "ongoing" @@ -44,15 +45,15 @@ class Transaction(BaseModel): started_at: datetime ended_at: Optional[datetime] = None meter_start: Decimal - meter_end: Optional[Decimal] = None + meter_end: Decimal end_reason: Optional[TransactionEventTriggerReason] = None price: Decimal - user_id: Optional[UUID] = None - chargepoint_id: UUID + user: UserThumb + chargepoint: ChargePointThumb class Config: from_attributes = True - json_encoders = {Decimal: decimal_encoder} + json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime} class RemoteTransactionStartStopResponse(BaseModel): status: RemoteTransactionStartStopStatus diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 8737aa4..c113e88 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -30,6 +30,13 @@ class User(UserBase): class Config: from_attributes = True +class UserThumb(BaseModel): + id: UUID + friendly_name: str + + class Config: + from_attributes = True + class PasswordUpdate(BaseModel): old_password: str = Field(max_length=100) new_password: str = Field(max_length=100) diff --git a/backend/app/services/transaction_service.py b/backend/app/services/transaction_service.py index d8719b4..9d3c992 100644 --- a/backend/app/services/transaction_service.py +++ b/backend/app/services/transaction_service.py @@ -21,19 +21,23 @@ async def create_transaction( with SessionLocal() as db: chargepoint = db.query(ChargePoint).filter(ChargePoint.identity == chargepoint_identity).first() meter_start=0 + meter_end=0 if "meter_value" in transaction_data.keys(): for meter_value_entry in transaction_data['meter_value']: for sampled_value in meter_value_entry['sampled_value']: if "measurand" in sampled_value.keys(): if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): meter_start = sampled_value['value'] + meter_end = sampled_value['value'] else: meter_start = sampled_value['value'] + meter_end = sampled_value['value'] transaction = Transaction( id=transaction_info["transaction_id"], status=TransactionStatus.ONGOING, started_at=timestamp, meter_start=meter_start, + meter_end=meter_end, price=chargepoint.price, chargepoint_id=chargepoint.id, user_id=user_id @@ -55,6 +59,12 @@ async def update_transaction( transaction_id=transaction.id, meter_value_data=meter_value_entry ) + # Update current meter_end value + for sampled_value in meter_value_entry['sampled_value']: + if "measurand" in sampled_value.keys(): + if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): + transaction.meter_end = sampled_value['value'] + db.commit() async def end_transaction( transaction_id: str, diff --git a/backend/app/util/encoders.py b/backend/app/util/encoders.py index fbdec8d..0f0d21f 100644 --- a/backend/app/util/encoders.py +++ b/backend/app/util/encoders.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from decimal import Decimal from typing import Union @@ -19,4 +20,8 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: if isinstance(exponent, int) and exponent >= 0: return int(dec_value) else: - return float(dec_value) \ No newline at end of file + return float(dec_value) + +def force_utc_datetime(datetime_value: datetime) -> datetime: + """Force a datetime to be in the UTC timzone""" + return datetime_value.replace(tzinfo=timezone.utc) \ No newline at end of file diff --git a/frontend/src/lib/component/DashboardCard.svelte b/frontend/src/lib/component/DashboardCard.svelte index 2b77f43..c476707 100644 --- a/frontend/src/lib/component/DashboardCard.svelte +++ b/frontend/src/lib/component/DashboardCard.svelte @@ -25,18 +25,20 @@
{props.value}{#if props.unit}{' ' + props.unit}{/if}
- {#if diff > 0} -{$i18n.t('common:transactionTable.startTime', { - time: transaction.begin, + time: new Date(transaction.started_at), formatParams: { time: { year: 'numeric', @@ -51,7 +42,7 @@
{$i18n.t('common:transactionTable.endTime', { - time: transaction.end, + time: new Date(transaction.ended_at!), formatParams: { time: { year: 'numeric', @@ -70,11 +61,15 @@
- {#if hasActiveTransaction}0,25 kWh{:else}-{/if} + {#if dashboardData.current_transaction}{dashboardData.current_transaction + .meter_end - dashboardData.current_transaction.meter_start} kWh{:else}-{/if}
- {#if hasActiveTransaction} + {#if dashboardData.current_transaction}- {#if hasActiveTransaction} - {$i18n.t('dashboard:cards.chargepoint', { name: 'DE-EXMPL-0001' })} + {#if dashboardData.current_transaction} + {$i18n.t('dashboard:cards.chargepoint', { + name: dashboardData.current_transaction.chargepoint.identity, + })} {:else} {$i18n.t('dashboard:cards.noCurrentTransaction')} {/if}
- {#if hasActiveTransaction} + {#if dashboardData.current_transaction}{$i18n.t('dashboard:table.title')}
-- {$i18n.t('common:transactionTable.startTime', { - time: transaction.begin, - formatParams: { - time: { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - }, - }, - })} -
-- {$i18n.t('common:transactionTable.endTime', { - time: transaction.end, - formatParams: { - time: { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - }, - }, - })} -
-