Bulk add, delete + BulkEntry add, delete

This commit is contained in:
Patrick Haßel 2022-10-24 12:53:34 +02:00
parent 8e5d639bbe
commit 162e1fd2f3
37 changed files with 281 additions and 117 deletions

View File

@ -3,6 +3,7 @@ import {validateNumberNotNull, validateStringNotEmptyNotNull} from "./validators
export class SearchResult {
constructor(
readonly type: string,
readonly id: number,
readonly title: string,
) {
@ -10,6 +11,7 @@ export class SearchResult {
static fromJson(json: any): SearchResult {
return new SearchResult(
validateStringNotEmptyNotNull(json['type']),
validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']),
);

View File

@ -7,6 +7,7 @@ import {CompatClient, Stomp} from "@stomp/stompjs";
import {Update} from "./Update";
import {LocationStrategy} from "@angular/common";
import {Page} from "./Page";
import {Next} from "./types";
export function NO_OP() {
}
@ -81,6 +82,10 @@ export class ApiService {
this.http.post<any>(this.restUrl(path), data).pipe(map(list => list.map(fromJson))).subscribe(next, errorInterceptor(error));
}
delete(path: string, next: Next<void>): void {
this.http.delete<void>(this.restUrl(path)).subscribe(next);
}
private restUrl(path: string): string {
return environment.restBase + this.locationStrategy.getBaseHref() + path;
}

View File

@ -8,7 +8,7 @@ export class BulkEntry {
readonly version: number,
readonly position: number,
readonly enabled: boolean,
readonly property: Property,
readonly property: Property | null,
readonly value: number,
) {
// nothing
@ -20,7 +20,7 @@ export class BulkEntry {
validateNumberNotNull(json['version']),
validateNumberNotNull(json['position']),
validateBooleanNotNull(json['enabled']),
Property.fromJson(json['property']),
Property.fromJsonOrNull(json['property']),
validateNumberNotNull(json['value']),
);
}

View File

@ -0,0 +1,19 @@
import {Property} from "../property/Property";
import {Bulk} from "./Bulk";
export class BulkEntryCreate {
readonly bulkId: number;
readonly position: number;
public constructor(
bulk: Bulk,
readonly enabled: boolean,
readonly property: Property | null,
readonly value: number,
) {
this.bulkId = bulk.id;
this.position = bulk.entries.length;
}
}

View File

@ -5,6 +5,19 @@ import {SearchResult} from "../SearchResult";
import {Update} from "../Update";
import {Bulk} from "./Bulk";
import {BulkEntry} from "./BulkEntry";
import {Next} from "../types";
import {BulkEntryCreate} from "./BulkEntryCreate";
class BulkCreate {
constructor(
readonly name: string,
readonly enabled: boolean,
) {
// nothing
}
}
@Injectable({
providedIn: 'root'
@ -42,7 +55,7 @@ export class BulkService implements ISearchService {
}
create(next: (item: Bulk) => void, error: (error: any) => void = NO_OP): void {
this.api.getReturnItem("bulk/create/", Bulk.fromJson, next, error);
this.api.postReturnItem("bulk/create/", new BulkCreate("Neu", true), Bulk.fromJson, next, error);
}
delete(bulk: Bulk, next: () => void, error: (error: any) => void = NO_OP): void {
@ -53,4 +66,13 @@ export class BulkService implements ISearchService {
this.api.putReturnItem("BulkEntry/" + entry.id + "/set/" + key, value, next, error);
}
deleteEntry(entry: BulkEntry, next: Next<void>): void {
this.api.delete("BulkEntry/" + entry.id, next);
}
createEntry(bulk: Bulk, next: Next<BulkEntry>): void {
const dto: BulkEntryCreate = new BulkEntryCreate(bulk, false, null, 0);
this.api.postReturnItem("BulkEntry/create", dto, BulkEntry.fromJson, next);
}
}

View File

@ -1,5 +1,6 @@
import {validateDateAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
import {validateDateAllowNull, validateListOrEmpty, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
import {Channel} from "../channel/Channel";
import {SearchResult} from "../SearchResult";
export class Property {
@ -11,6 +12,7 @@ export class Property {
public timestamp: Date | null,
public readChannel: Channel | null,
public writeChannel: Channel | null,
public usages: SearchResult[],
) {
// nothing
}
@ -31,9 +33,17 @@ export class Property {
validateDateAllowNull(json['timestamp']),
Channel.fromJsonAllowNull(json['readChannel']),
Channel.fromJsonAllowNull(json['writeChannel']),
validateListOrEmpty(json['usages'], SearchResult.fromJson),
);
}
static fromJsonOrNull(json: any): Property | null {
if (json === null) {
return null;
}
return this.fromJson(json);
}
public static trackBy(index: number, item: Property): number {
return item.id;
}

View File

@ -4,6 +4,7 @@ import {ISearchService} from "../ISearchService";
import {SearchResult} from "../SearchResult";
import {Update} from "../Update";
import {Property} from "./Property";
import {Next} from "../types";
@Injectable({
providedIn: 'root'
@ -40,4 +41,8 @@ export class PropertyService implements ISearchService {
this.api.getReturnItem("property/create/", Property.fromJson, next, error);
}
delete(property: Property, next: Next<void>): void {
this.api.delete("property/" + property.id, next);
}
}

View File

@ -1,46 +1,60 @@
<table>
<tr>
<th>Eigenschaft</th>
<th>Wert</th>
</tr>
<ng-container *ngIf="bulk">
<tr *ngFor="let entry of bulk.entries">
<td>
<app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search>
</td>
<ng-container [ngSwitch]="entry.property?.type">
<td *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="setEntry(entry, 'value', entry.value > 0 ? 0 : 1)">
{{entry.value ? "An" : "Aus"}}
</td>
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option [ngValue]="0">100% Offen</option>
<option [ngValue]="35">&nbsp;50%</option>
<option [ngValue]="55">&nbsp;75%</option>
<option [ngValue]="75">&nbsp;90% Sonnenschutz</option>
<option [ngValue]="85">100% Schlitze</option>
<option [ngValue]="100">100% Geschlossen</option>
</select>
</td>
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchDefault class="empty">
&nbsp;
</td>
</ng-container>
<ng-container *ngIf="bulk">
<h1>{{bulk.name}}</h1>
<table>
<tr>
<th>Eigenschaft</th>
<th>Wert</th>
<th>&nbsp;</th>
</tr>
</ng-container>
</table>
<ng-container *ngIf="bulk">
<tr *ngFor="let entry of bulk.entries">
<td>
<app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search>
</td>
<ng-container [ngSwitch]="entry.property?.type">
<td *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="setEntry(entry, 'value', entry.value > 0 ? 0 : 1)">
{{entry.value ? "An" : "Aus"}}
</td>
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option [ngValue]="0">100% Offen</option>
<option [ngValue]="35">&nbsp;50%</option>
<option [ngValue]="55">&nbsp;75%</option>
<option [ngValue]="75">&nbsp;90% Sonnenschutz</option>
<option [ngValue]="85">100% Schlitze</option>
<option [ngValue]="100">100% Geschlossen</option>
</select>
</td>
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', entry.value)">
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
</select>
</td>
<td *ngSwitchDefault class="empty">
&nbsp;
</td>
<td class="delete">
<fa-icon title="Löschen" [icon]="faTimes" (click)="delete(entry)"></fa-icon>
</td>
</ng-container>
</tr>
</ng-container>
</table>
<p>
<button (click)="create()">+ Hinzufügen</button>
</p>
</ng-container>

View File

@ -4,6 +4,7 @@ import {Bulk} from "../../../api/bulk/Bulk";
import {ActivatedRoute} from "@angular/router";
import {PropertyService} from "../../../api/property/property.service";
import {BulkEntry} from "../../../api/bulk/BulkEntry";
import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
@Component({
selector: 'app-bulk',
@ -12,6 +13,8 @@ import {BulkEntry} from "../../../api/bulk/BulkEntry";
})
export class BulkComponent implements OnInit {
readonly faTimes = faTimesCircle;
bulk!: Bulk;
constructor(
@ -44,4 +47,14 @@ export class BulkComponent implements OnInit {
}
}
delete(entry: BulkEntry): void {
if (confirm(`Eintrag #"${entry.position}" wirklich löschen?`)) {
this.bulkService.deleteEntry(entry, () => this.bulk.entries.splice(this.bulk.entries.findIndex(e => e.id === entry.id), 1));
}
}
create(): void {
this.bulkService.createEntry(this.bulk, entry => this.update(entry));
}
}

View File

@ -14,12 +14,17 @@
<th>Zeitstempel</th>
<th>Lesekanal</th>
<th>Schreibkanal</th>
<th>
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
</th>
</tr>
<ng-container *ngFor="let property of properties.sort(Property.compareTypeThenTitle)">
<tr>
<td>
<app-edit-field [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-edit-field>
</td>
<td>
<select [(ngModel)]="property.type" (ngModelChange)="edit(property, 'type', property.type)">
<option value="BOOLEAN">Schalter</option>
@ -30,6 +35,7 @@
<option value="SCENE">Szene</option>
</select>
</td>
<ng-container *ngIf="property.value !== null else empty">
<td *ngIf="property.type === 'BOOLEAN'" class="boolean" [class.true]="property.value" [class.false]="!property.value" (click)="edit(property, 'value', property.value > 0 ? 0 : 1)">
{{property.value ? "An" : "Aus"}}
@ -50,15 +56,22 @@
{{findScene(property)?.title || "Unbekannt: " + property.value}}
</td>
</ng-container>
<td *ngIf="property.timestamp !== null else empty">
{{property.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}
</td>
<td class="full">
<app-search [searchService]="channelService" [initial]="property.readChannel?.id" (valueChange)="set(property, 'readChannel', $event)"></app-search>
</td>
<td class="full">
<app-search [searchService]="channelService" [initial]="property.writeChannel?.id" (valueChange)="set(property, 'writeChannel', $event)"></app-search>
</td>
<td class="delete">
<fa-icon title="Löschen" *ngIf="property.usages.length === 0" [icon]="faTimes" (click)="delete(property)"></fa-icon>
</td>
</tr>
</ng-container>
</table>

View File

@ -4,6 +4,7 @@ import {PropertyService} from "../../api/property/property.service";
import {Scene} from "../../api/scene/Scene";
import {SceneService} from "../../api/scene/scene.service";
import {ChannelService} from "../../api/channel/channel.service";
import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
@Component({
selector: 'app-property-list',
@ -12,6 +13,8 @@ import {ChannelService} from "../../api/channel/channel.service";
})
export class PropertyListComponent implements OnInit {
readonly faTimes = faTimesCircle;
Property = Property;
properties: Property[] = [];
@ -80,4 +83,10 @@ export class PropertyListComponent implements OnInit {
this.propertyService.create(property => this.updateProperty(property, true));
}
delete(property: Property): void {
if (confirm(`Eigenschaft "${property.title}" wirklich löschen?`)) {
this.propertyService.delete(property, () => this.properties.splice(this.properties.findIndex(p => p.id === property.id), 1));
}
}
}

View File

@ -98,6 +98,7 @@ table.vertical {
.delete {
color: darkred;
text-align: center;
}
.disabled {

View File

@ -18,6 +18,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.ZonedDateTime;
@SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"})
@Slf4j
@Service
@ -52,7 +54,8 @@ public class DemoDataService {
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBrightness.getId(), 40, 0));
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkColorTemperature.getId(), 55, 0));
final long scheduleId = createSchedule(true, "schedule");
createTime(scheduleId, true, 12, 0, 0, 0, propertyDirect, 1, bulk);
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(3);
createTime(scheduleId, true, now.getHour(), now.getMinute(), now.getSecond(), 0, propertyDirect, 1, bulk);
}
private Property createProperty(final String title, final PropertyType type, final Channel readChannel, final Channel writeChannel) {

View File

@ -72,7 +72,7 @@ public class BulkController implements ISearchController {
}
private SearchResult toSearchResult(final BulkDto bulkDto) {
return new SearchResult(bulkDto.getId(), bulkDto.getName());
return new SearchResult(Bulk.class, bulkDto.getId(), bulkDto.getName());
}
}

View File

@ -15,7 +15,7 @@ public class BulkExecutor {
private final PropertyWriteService propertyWriteService;
public void execute(final Bulk bulk) {
log.debug("Executing Bulk: {}", bulk);
log.info("Executing Bulk: {}", bulk);
bulk.getEntries().forEach(entry -> propertyWriteService.writeToChannel(entry.getProperty(), entry.getValue()));
log.debug("Finished executing Bulk: {}", bulk);
}

View File

@ -34,7 +34,7 @@ public class BulkMapper {
}
public BulkEntryDto toDto(final BulkEntry entry) {
final PropertyDto property = propertyMapper.toDto(entry.getProperty());
final PropertyDto property = propertyMapper.toDtoOrNull(entry.getProperty());
return new BulkEntryDto(entry, property);
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -46,4 +47,8 @@ public class BulkReader {
return bulkRepository.findByEntries_Id(id).orElseThrow(NotFoundException.supply(Bulk.class, "entries_id", id));
}
public List<Bulk> findAllByProperty(final Property property) {
return bulkRepository.findDistinctByEntries_Property(property);
}
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.Property;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@ -13,4 +14,6 @@ public interface BulkRepository extends JpaRepository<Bulk, Long>, JpaSpecificat
Optional<Bulk> findByEntries_Id(long id);
List<Bulk> findDistinctByEntries_Property(Property property);
}

View File

@ -28,7 +28,7 @@ public class BulkEntry {
private int position;
@Setter
@ManyToOne(optional = false)
@ManyToOne
private Property property;
@Setter

View File

@ -22,8 +22,8 @@ public class BulkEntryController {
}
@PutMapping("{id}/set/property")
public BulkEntryDto setValue(@PathVariable final long id, @RequestBody final long propertyId) {
return bulkMapper.toDto(bulkEntryWriter.set(id, entry -> entry.setProperty(propertyReader.getById(propertyId))));
public BulkEntryDto setValue(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
return bulkMapper.toDto(bulkEntryWriter.set(id, entry -> entry.setProperty(propertyId == null ? null : propertyReader.getById(propertyId))));
}
@PutMapping("{id}/set/value")

View File

@ -56,7 +56,9 @@ public class BulkEntryWriter {
}
public void remove(final long id) {
getEntryById(id);
final Bulk bulk = bulkReader.getBulkByEntryId(id);
final BulkEntry entry = getEntryById(bulk, id);
bulk.getEntries().remove(entry);
}
private BulkEntry getEntryById(final long id) {

View File

@ -34,7 +34,7 @@ public class ChannelController implements ISearchController {
}
private SearchResult toSearchResult(final ChannelDto dto) {
return new SearchResult(dto.getId(), dto.getTitle());
return new SearchResult(Channel.class, dto.getId(), dto.getTitle());
}
}

View File

@ -87,7 +87,7 @@ public class PropertyController implements ISearchController {
}
private SearchResult toSearchResult(final PropertyDto propertyDto) {
return new SearchResult(propertyDto.getId(), propertyDto.getTitle());
return new SearchResult(Property.class, propertyDto.getId(), propertyDto.getTitle());
}
}

View File

@ -1,10 +1,12 @@
package de.ph87.homeautomation.property;
import de.ph87.homeautomation.channel.ChannelDto;
import de.ph87.homeautomation.shared.SearchResult;
import lombok.Data;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.List;
@Data
public final class PropertyDto implements Serializable {
@ -23,7 +25,9 @@ public final class PropertyDto implements Serializable {
private final ChannelDto writeChannel;
public PropertyDto(final Property property, final ChannelDto readChannel, final ChannelDto writeChannel) {
private final List<SearchResult> usages;
public PropertyDto(final Property property, final ChannelDto readChannel, final ChannelDto writeChannel, final List<SearchResult> usages) {
this.id = property.getId();
this.type = property.getType();
this.title = property.getTitle();
@ -31,6 +35,7 @@ public final class PropertyDto implements Serializable {
this.timestamp = property.getTimestamp();
this.readChannel = readChannel;
this.writeChannel = writeChannel;
this.usages = usages;
}
}

View File

@ -1,11 +1,20 @@
package de.ph87.homeautomation.property;
import de.ph87.homeautomation.bulk.Bulk;
import de.ph87.homeautomation.bulk.BulkRepository;
import de.ph87.homeautomation.channel.ChannelService;
import de.ph87.homeautomation.schedule.Schedule;
import de.ph87.homeautomation.schedule.ScheduleRepository;
import de.ph87.homeautomation.shared.SearchResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@Transactional
@ -14,14 +23,27 @@ public class PropertyMapper {
private final ChannelService channelService;
private final ScheduleRepository scheduleRepository;
private final BulkRepository bulkRepository;
public PropertyDto toDto(final Property property) {
final List<SearchResult> usages = findUsages(property);
return new PropertyDto(
property,
channelService.toDtoAllowNull(property.getReadChannel()),
channelService.toDtoAllowNull(property.getWriteChannel())
channelService.toDtoAllowNull(property.getWriteChannel()),
usages
);
}
private List<SearchResult> findUsages(final Property property) {
final List<SearchResult> searchResults = new ArrayList<>();
searchResults.addAll(scheduleRepository.findDistinctByEntries_Property(property).stream().map(this::toSearchResult).collect(Collectors.toList()));
searchResults.addAll(bulkRepository.findDistinctByEntries_Property(property).stream().map(this::toSearchResult).collect(Collectors.toList()));
return searchResults;
}
public PropertyDto toDtoOrNull(final Property property) {
if (property == null) {
return null;
@ -29,4 +51,12 @@ public class PropertyMapper {
return toDto(property);
}
private SearchResult toSearchResult(final Schedule schedule) {
return new SearchResult(Schedule.class, schedule.getId(), schedule.getTitle());
}
private SearchResult toSearchResult(final Bulk bulk) {
return new SearchResult(Bulk.class, bulk.getId(), bulk.getName());
}
}

View File

@ -27,7 +27,7 @@ public class Schedule {
private String title;
@ToString.Exclude
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "schedule")
private Set<ScheduleEntry> entries = new HashSet<>();
}

View File

@ -29,14 +29,14 @@ public class ScheduleCalculationService {
private final Config config;
private final ScheduleReadService scheduleReadService;
private final ScheduleReader scheduleReader;
private final ApplicationEventPublisher applicationEventPublisher;
@EventListener(ApplicationStartedEvent.class)
public void calculateAllNext() {
final ZonedDateTime now = ZonedDateTime.now();
scheduleReadService.findAll().forEach(schedule -> calculateSchedule(schedule, now));
scheduleReader.findAll().forEach(schedule -> calculateSchedule(schedule, now));
}
public void calculateSchedule(final Schedule schedule, final ZonedDateTime now) {

View File

@ -11,7 +11,7 @@ import java.util.List;
@RequiredArgsConstructor
public class ScheduleController {
private final ScheduleReadService scheduleReadService;
private final ScheduleReader scheduleReader;
private final ScheduleWriteService scheduleWriteService;
@ -19,12 +19,12 @@ public class ScheduleController {
@GetMapping("findAll")
public List<ScheduleDto> findAll() {
return scheduleReadService.findAllDtos();
return scheduleReader.findAllDtos();
}
@GetMapping("searchById/{id}")
public ScheduleDto getById(@PathVariable final long id) {
return scheduleReadService.getDtoById(id);
return scheduleReader.getDtoById(id);
}
@GetMapping("create")

View File

@ -17,7 +17,7 @@ import java.util.Comparator;
@RequiredArgsConstructor
public class ScheduleExecutionService {
private final ScheduleReadService scheduleReadService;
private final ScheduleReader scheduleReader;
private final ScheduleCalculationService scheduleCalculationService;
@ -27,7 +27,7 @@ public class ScheduleExecutionService {
public void executeAllLastDue() {
final ZonedDateTime now = ZonedDateTime.now();
scheduleReadService.findAll().forEach(schedule -> executeLastDue(schedule, now));
scheduleReader.findAll().forEach(schedule -> executeLastDue(schedule, now));
}
private void executeLastDue(final Schedule schedule, final ZonedDateTime now) {

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor;
@ -14,7 +15,7 @@ import java.util.stream.Collectors;
@Service
@Transactional
@RequiredArgsConstructor
public class ScheduleReadService {
public class ScheduleReader {
private final ScheduleRepository scheduleRepository;
@ -40,4 +41,8 @@ public class ScheduleReadService {
return scheduleMapper.toDto(getById(id));
}
public List<Schedule> findAllByProperty(final Property property) {
return scheduleRepository.findDistinctByEntries_Property(property);
}
}

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import org.springframework.data.repository.CrudRepository;
@ -13,4 +14,6 @@ public interface ScheduleRepository extends CrudRepository<Schedule, Long> {
boolean existsByTitle(String title);
List<Schedule> findDistinctByEntries_Property(Property property);
}

View File

@ -1,6 +1,6 @@
package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryReadService;
import de.ph87.homeautomation.schedule.entry.ScheduleEntryReader;
import de.ph87.homeautomation.shared.AbstractThreadService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -17,7 +17,7 @@ public class ScheduleThreadService extends AbstractThreadService {
private final ScheduleExecutionService scheduleExecutionService;
private final ScheduleEntryReadService scheduleEntryReadService;
private final ScheduleEntryReader scheduleEntryReader;
@Override
protected String getThreadName() {
@ -32,7 +32,7 @@ public class ScheduleThreadService extends AbstractThreadService {
@Override
protected long doStep() throws InterruptedException {
scheduleExecutionService.executeAllLastDue();
return scheduleEntryReadService.getNextTimestamp().map(nextTimestamp -> Duration.between(ZonedDateTime.now(), nextTimestamp).toMillis()).orElse(0L);
return scheduleEntryReader.getNextTimestamp().map(nextTimestamp -> Duration.between(ZonedDateTime.now(), nextTimestamp).toMillis()).orElse(0L);
}
@Override

View File

@ -17,7 +17,7 @@ public class ScheduleWriteService {
public static final String NAME_PREFIX = "Neu ";
private final ScheduleReadService scheduleReadService;
private final ScheduleReader scheduleReader;
private final ScheduleCalculationService scheduleCalculationService;
@ -43,14 +43,14 @@ public class ScheduleWriteService {
}
public <T> ScheduleDto set(final long id, final BiConsumer<Schedule, T> setter, final T value) {
final Schedule schedule = scheduleReadService.getById(id);
final Schedule schedule = scheduleReader.getById(id);
setter.accept(schedule, value);
scheduleCalculationService.calculateSchedule(schedule, ZonedDateTime.now());
return publish(schedule, true);
}
public void delete(final long id) {
final Schedule schedule = scheduleReadService.getById(id);
final Schedule schedule = scheduleReader.getById(id);
scheduleRepository.delete(schedule);
publish(schedule, false);
}

View File

@ -3,8 +3,10 @@ package de.ph87.homeautomation.schedule.entry;
import com.luckycatlabs.sunrisesunset.Zenith;
import de.ph87.homeautomation.bulk.Bulk;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.Schedule;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@ -14,6 +16,7 @@ import java.util.Random;
@Getter
@Setter
@Entity
@NoArgsConstructor
public class ScheduleEntry {
private static final Random RANDOM = new Random(System.currentTimeMillis());
@ -23,6 +26,9 @@ public class ScheduleEntry {
@Setter(AccessLevel.NONE)
private Long id;
@ManyToOne
private Schedule schedule;
private boolean enabled = false;
private boolean monday = true;
@ -71,6 +77,10 @@ public class ScheduleEntry {
@Setter(AccessLevel.NONE)
private ZonedDateTime nextFuzzyTimestamp;
public ScheduleEntry(final Schedule schedule) {
this.schedule = schedule;
}
public void setNextClearTimestamp(final ZonedDateTime next) {
nextClearTimestamp = next;
if (nextClearTimestamp == null) {
@ -82,24 +92,6 @@ public class ScheduleEntry {
}
}
public void setWorkday(final boolean enabled) {
monday = enabled;
tuesday = enabled;
wednesday = enabled;
thursday = enabled;
friday = enabled;
}
public void setWeekend(final boolean enabled) {
saturday = enabled;
sunday = enabled;
}
public void setEveryday(final boolean enabled) {
setWorkday(enabled);
setWeekend(enabled);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@ -111,13 +103,13 @@ public class ScheduleEntry {
builder.append(", type=");
builder.append(type);
builder.append(", weekdays=");
builder.append(monday ? 1 : 0);
builder.append(tuesday ? 1 : 0);
builder.append(wednesday ? 1 : 0);
builder.append(thursday ? 1 : 0);
builder.append(friday ? 1 : 0);
builder.append(saturday ? 1 : 0);
builder.append(sunday ? 1 : 0);
builder.append(monday ? "Mon" : "___");
builder.append(tuesday ? "Tue" : "___");
builder.append(wednesday ? "Wed" : "___");
builder.append(thursday ? "Thu" : "___");
builder.append(friday ? "Fri" : "___");
builder.append(saturday ? "Sat" : "___");
builder.append(sunday ? "Sun" : "___");
if (type != null) {
switch (type) {
case TIME:

View File

@ -13,7 +13,7 @@ import java.util.Optional;
@Service
@Transactional
@RequiredArgsConstructor
public class ScheduleEntryReadService {
public class ScheduleEntryReader {
private final ScheduleEntryRepository scheduleEntryRepository;

View File

@ -2,7 +2,7 @@ package de.ph87.homeautomation.schedule.entry;
import de.ph87.homeautomation.schedule.Schedule;
import de.ph87.homeautomation.schedule.ScheduleCalculationService;
import de.ph87.homeautomation.schedule.ScheduleReadService;
import de.ph87.homeautomation.schedule.ScheduleReader;
import de.ph87.homeautomation.web.WebSocketService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -19,9 +19,9 @@ import java.util.function.Consumer;
@RequiredArgsConstructor
public class ScheduleEntryWriteService {
private final ScheduleEntryReadService scheduleEntryReadService;
private final ScheduleEntryReader scheduleEntryReader;
private final ScheduleReadService scheduleReadService;
private final ScheduleReader scheduleReader;
private final ScheduleCalculationService scheduleCalculationService;
@ -33,31 +33,31 @@ public class ScheduleEntryWriteService {
@Deprecated(since = "Use alternative 'set' instead.", forRemoval = true)
public <T> ScheduleEntryDto set(final long id, final BiConsumer<ScheduleEntry, T> setter, final T value) {
final ScheduleEntry entry = scheduleEntryReadService.getById(id);
final ScheduleEntry entry = scheduleEntryReader.getById(id);
setter.accept(entry, value);
final Schedule schedule = scheduleReadService.getByEntry(entry);
final Schedule schedule = scheduleReader.getByEntry(entry);
scheduleCalculationService.calculateSchedule(schedule, ZonedDateTime.now());
return publish(entry, true);
}
public ScheduleEntryDto set(final long id, final Consumer<ScheduleEntry> setter) {
final ScheduleEntry entry = scheduleEntryReadService.getById(id);
final ScheduleEntry entry = scheduleEntryReader.getById(id);
setter.accept(entry);
final Schedule schedule = scheduleReadService.getByEntry(entry);
final Schedule schedule = scheduleReader.getByEntry(entry);
scheduleCalculationService.calculateSchedule(schedule, ZonedDateTime.now());
return publish(entry, true);
}
public ScheduleEntryDto create(final long scheduleId) {
final Schedule schedule = scheduleReadService.getById(scheduleId);
final ScheduleEntry entry = new ScheduleEntry();
schedule.getEntries().add(scheduleEntryRepository.save(entry));
final Schedule schedule = scheduleReader.getById(scheduleId);
final ScheduleEntry entry = scheduleEntryRepository.save(new ScheduleEntry(schedule));
schedule.getEntries().add(entry);
return publish(entry, true);
}
public void delete(final long id) {
final ScheduleEntry entry = scheduleEntryReadService.getById(id);
scheduleReadService.getByEntry(entry).getEntries().remove(entry);
final ScheduleEntry entry = scheduleEntryReader.getById(id);
scheduleReader.getByEntry(entry).getEntries().remove(entry);
scheduleEntryRepository.delete(entry);
publish(entry, false);
}

View File

@ -5,11 +5,14 @@ import lombok.Getter;
@Getter
public class SearchResult {
public final long id;
private final String type;
public final String title;
private final long id;
public SearchResult(final long id, final String title) {
private final String title;
public SearchResult(final Class<?> clazz, final long id, final String title) {
this.type = clazz.getSimpleName();
this.id = id;
this.title = title;
}