new design with dynamic pricing
This commit is contained in:
parent
0169576620
commit
e0d6d04409
|
|
@ -21,7 +21,13 @@ public class Configuration
|
|||
public double? TimeChargeandDischargePower { get; set; }
|
||||
public DateTime? StartTimeChargeandDischargeDayandTime { get; set; }
|
||||
public DateTime? StopTimeChargeandDischargeDayandTime { get; set; }
|
||||
|
||||
|
||||
// Sinexcel Dynamic Pricing (under GridPriority) — strings for demo; engine will parse later.
|
||||
public string? DynamicPricingMode { get; set; }
|
||||
public string? CurrentPrice { get; set; }
|
||||
public string? PriceToSell { get; set; }
|
||||
public string? PriceToBuy { get; set; }
|
||||
|
||||
public String GetConfigurationString()
|
||||
{
|
||||
return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " +
|
||||
|
|
@ -48,7 +54,8 @@ public class Configuration
|
|||
{
|
||||
return $"MinimumSoC: {MinimumSoC}, MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " +
|
||||
$"InverterNumber: {InverterNumber}, BatteriesCount: {BatteriesCount}, BatteriesCountPerInverter: [{(BatteriesCountPerInverter != null ? string.Join(", ", BatteriesCountPerInverter) : "")}], ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, ControlPermission:{ControlPermission}, "+
|
||||
$"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}";
|
||||
$"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}, " +
|
||||
$"DynamicPricingMode: {DynamicPricingMode}, CurrentPrice: {CurrentPrice}, PriceToSell: {PriceToSell}, PriceToBuy: {PriceToBuy}";
|
||||
}
|
||||
|
||||
// TODO: SodistoreGrid — update configuration fields when defined
|
||||
|
|
|
|||
|
|
@ -709,6 +709,12 @@ export type ConfigurationValues = {
|
|||
timeChargeandDischargePower?: number;
|
||||
startTimeChargeandDischargeDayandTime?: Date | null;
|
||||
stopTimeChargeandDischargeDayandTime?: Date | null;
|
||||
|
||||
// For sodistoreHome-Sinexcel: Dynamic Pricing (under GridPriority)
|
||||
dynamicPricingMode?: string;
|
||||
currentPrice?: string;
|
||||
priceToSell?: string;
|
||||
priceToBuy?: string;
|
||||
};
|
||||
//
|
||||
// export interface Pv {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
FormControl,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Modal,
|
||||
Select,
|
||||
|
|
@ -26,7 +27,12 @@ import axiosConfig from '../../../Resources/axiosConfig';
|
|||
import { UserContext } from '../../../contexts/userContext';
|
||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import { INSTALLATION_PRESETS } from '../Information/installationSetupUtils';
|
||||
import {
|
||||
INSTALLATION_PRESETS,
|
||||
buildSodistoreProPreset,
|
||||
getPresetsForDevice,
|
||||
PresetConfig
|
||||
} from '../Information/installationSetupUtils';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import {DateTimePicker } from '@mui/x-date-pickers';
|
||||
|
|
@ -60,6 +66,14 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
'PvPriorityCharging': 'GridPriority',
|
||||
};
|
||||
|
||||
// Dynamic Pricing Mode — backend enum values with UI labels
|
||||
const DynamicPricingOptions = ['Disabled', 'SpotPrice', 'Tou'] as const;
|
||||
const dynamicPricingLabelKey: Record<string, string> = {
|
||||
Disabled: 'dynamicPricingOff',
|
||||
SpotPrice: 'dynamicPricingOn',
|
||||
Tou: 'dynamicPricingTou',
|
||||
};
|
||||
|
||||
const [errors, setErrors] = useState({
|
||||
minimumSoC: false,
|
||||
gridSetPoint: false
|
||||
|
|
@ -122,6 +136,10 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
return parsed && parsed.year() >= 2020 ? parsed.toDate() : new Date();
|
||||
})(),
|
||||
controlPermission: String(props.values.Config.ControlPermission).toLowerCase() === "true",
|
||||
dynamicPricingMode: (props.values.Config as any).DynamicPricingMode ?? 'Disabled',
|
||||
currentPrice: (props.values.Config as any).CurrentPrice?.toString() ?? '',
|
||||
priceToSell: (props.values.Config as any).PriceToSell?.toString() ?? '',
|
||||
priceToBuy: (props.values.Config as any).PriceToBuy?.toString() ?? '',
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -263,7 +281,11 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
stopTimeChargeandDischargeDayandTime: formValues.stopTimeChargeandDischargeDayandTime
|
||||
? new Date(formValues.stopTimeChargeandDischargeDayandTime.getTime() - formValues.stopTimeChargeandDischargeDayandTime.getTimezoneOffset() * 60000)
|
||||
: null,
|
||||
controlPermission:formValues.controlPermission
|
||||
controlPermission:formValues.controlPermission,
|
||||
dynamicPricingMode: formValues.dynamicPricingMode,
|
||||
currentPrice: formValues.currentPrice,
|
||||
priceToSell: formValues.priceToSell,
|
||||
priceToBuy: formValues.priceToBuy,
|
||||
};
|
||||
|
||||
setLoading(true);
|
||||
|
|
@ -441,12 +463,15 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
)}
|
||||
|
||||
{device === 4 && (() => {
|
||||
const preset = INSTALLATION_PRESETS[props.installation.installationModel];
|
||||
const inverterCount = preset ? preset.length : 1;
|
||||
const pvStrings = (props.installation.pvStringsPerInverter || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '');
|
||||
// Mirror Information tab's preset derivation so both views always agree.
|
||||
const isSodistorePro = product === 5;
|
||||
const model = props.installation.installationModel;
|
||||
const presetConfig: PresetConfig | null = isSodistorePro
|
||||
? (model && parseInt(model, 10) > 0
|
||||
? buildSodistoreProPreset(parseInt(model, 10))
|
||||
: null)
|
||||
: (getPresetsForDevice(device)[model] || null);
|
||||
const inverterCount = presetConfig?.length ?? 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -459,8 +484,10 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{preset && preset.map((clusters, i) => {
|
||||
const batteriesInInverter = clusters.reduce((a, b) => a + b, 0);
|
||||
{Array.from({ length: inverterCount }, (_, i) => {
|
||||
const batteriesInInverter = presetConfig
|
||||
? (presetConfig[i] ?? []).reduce((a, b) => a + b, 0)
|
||||
: 0;
|
||||
return (
|
||||
<div key={`battCount_${i}`} style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
|
|
@ -475,20 +502,6 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{Array.from({ length: inverterCount }, (_, i) => (
|
||||
<div key={`pvCount_${i}`} style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage(
|
||||
{ id: 'pvInInverter' },
|
||||
{ number: i + 1 }
|
||||
)}
|
||||
value={pvStrings[i] ?? 0}
|
||||
InputProps={{ readOnly: true }}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
|
@ -679,6 +692,114 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
|||
</>
|
||||
)}
|
||||
|
||||
{/* --- Sinexcel + GridPriority: Dynamic Pricing --- */}
|
||||
{device === 4 &&
|
||||
OperatingPriorityOptions[formValues.operatingPriority] === 'GridPriority' && (
|
||||
<>
|
||||
<Typography variant="h6" sx={{ mt: 3, mb: 1 }}>
|
||||
<FormattedMessage id="dynamicPricing" defaultMessage="Dynamic Pricing" />
|
||||
</Typography>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
||||
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
||||
<FormattedMessage id="dynamicPricingMode" defaultMessage="Dynamic Pricing Mode" />
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={formValues.dynamicPricingMode ?? 'Disabled'}
|
||||
onChange={(e) => {
|
||||
setFormDirty(true);
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
dynamicPricingMode: e.target.value as string,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{DynamicPricingOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{intl.formatMessage({ id: dynamicPricingLabelKey[opt] })}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{formValues.dynamicPricingMode === 'SpotPrice' && (
|
||||
<>
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'networkProvider' })}
|
||||
value={
|
||||
props.installation.networkProvider ||
|
||||
intl.formatMessage({ id: 'networkProviderSetOnInformationTab' })
|
||||
}
|
||||
InputProps={{ readOnly: true }}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'currentPrice' })}
|
||||
name="currentPrice"
|
||||
value={formValues.currentPrice ?? ''}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||
setFormDirty(true);
|
||||
setFormValues((prev) => ({ ...prev, currentPrice: v }));
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'priceToSell' })}
|
||||
name="priceToSell"
|
||||
value={formValues.priceToSell ?? ''}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||
setFormDirty(true);
|
||||
setFormValues((prev) => ({ ...prev, priceToSell: v }));
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '5px' }}>
|
||||
<TextField
|
||||
label={intl.formatMessage({ id: 'priceToBuy' })}
|
||||
name="priceToBuy"
|
||||
value={formValues.priceToBuy ?? ''}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||
setFormDirty(true);
|
||||
setFormValues((prev) => ({ ...prev, priceToBuy: v }));
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
|
|
|||
|
|
@ -529,6 +529,15 @@
|
|||
"systemSettings": "Systemeinstellungen",
|
||||
"pvPerInverter": "PV pro Wechselrichter",
|
||||
"pvInInverter": "PV in Wechselrichter {number}",
|
||||
"dynamicPricing": "Dynamische Preisgestaltung",
|
||||
"dynamicPricingMode": "Modus der dynamischen Preisgestaltung",
|
||||
"dynamicPricingOff": "Aus",
|
||||
"dynamicPricingOn": "Ein",
|
||||
"dynamicPricingTou": "TOU",
|
||||
"currentPrice": "Aktueller Preis",
|
||||
"priceToSell": "Verkaufspreis",
|
||||
"priceToBuy": "Kaufpreis",
|
||||
"networkProviderSetOnInformationTab": "Im Informations-Tab festlegen",
|
||||
"tourLanguageTitle": "Sprache",
|
||||
"tourLanguageContent": "Wählen Sie Ihre bevorzugte Sprache. Die Oberfläche unterstützt Englisch, Deutsch, Französisch und Italienisch.",
|
||||
"tourExploreTitle": "Installation erkunden",
|
||||
|
|
|
|||
|
|
@ -277,6 +277,15 @@
|
|||
"systemSettings": "System Settings",
|
||||
"pvPerInverter": "PV per Inverter",
|
||||
"pvInInverter": "PV in Inverter {number}",
|
||||
"dynamicPricing": "Dynamic Pricing",
|
||||
"dynamicPricingMode": "Dynamic Pricing Mode",
|
||||
"dynamicPricingOff": "Off",
|
||||
"dynamicPricingOn": "On",
|
||||
"dynamicPricingTou": "TOU",
|
||||
"currentPrice": "Current Price",
|
||||
"priceToSell": "Price to Sell",
|
||||
"priceToBuy": "Price to Buy",
|
||||
"networkProviderSetOnInformationTab": "Set on Information tab",
|
||||
"tourLanguageTitle": "Language",
|
||||
"tourLanguageContent": "Choose your preferred language. The interface supports English, German, French, and Italian.",
|
||||
"tourExploreTitle": "Explore an Installation",
|
||||
|
|
|
|||
|
|
@ -529,6 +529,15 @@
|
|||
"systemSettings": "Paramètres système",
|
||||
"pvPerInverter": "PV par onduleur",
|
||||
"pvInInverter": "PV dans l'onduleur {number}",
|
||||
"dynamicPricing": "Tarification dynamique",
|
||||
"dynamicPricingMode": "Mode de tarification dynamique",
|
||||
"dynamicPricingOff": "Désactivé",
|
||||
"dynamicPricingOn": "Activé",
|
||||
"dynamicPricingTou": "TOU",
|
||||
"currentPrice": "Prix actuel",
|
||||
"priceToSell": "Prix de vente",
|
||||
"priceToBuy": "Prix d'achat",
|
||||
"networkProviderSetOnInformationTab": "À définir dans l'onglet Informations",
|
||||
"tourLanguageTitle": "Langue",
|
||||
"tourLanguageContent": "Choisissez votre langue préférée. L'interface est disponible en anglais, allemand, français et italien.",
|
||||
"tourExploreTitle": "Explorer une installation",
|
||||
|
|
|
|||
|
|
@ -529,6 +529,15 @@
|
|||
"systemSettings": "Impostazioni di sistema",
|
||||
"pvPerInverter": "PV per inverter",
|
||||
"pvInInverter": "PV nell'inverter {number}",
|
||||
"dynamicPricing": "Prezzi dinamici",
|
||||
"dynamicPricingMode": "Modalità prezzi dinamici",
|
||||
"dynamicPricingOff": "Off",
|
||||
"dynamicPricingOn": "On",
|
||||
"dynamicPricingTou": "TOU",
|
||||
"currentPrice": "Prezzo attuale",
|
||||
"priceToSell": "Prezzo di vendita",
|
||||
"priceToBuy": "Prezzo di acquisto",
|
||||
"networkProviderSetOnInformationTab": "Imposta nella scheda Informazioni",
|
||||
"tourLanguageTitle": "Lingua",
|
||||
"tourLanguageContent": "Scegli la tua lingua preferita. L'interfaccia supporta inglese, tedesco, francese e italiano.",
|
||||
"tourExploreTitle": "Esplora un'installazione",
|
||||
|
|
|
|||
Loading…
Reference in New Issue