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 { export class SearchResult {
constructor( constructor(
readonly type: string,
readonly id: number, readonly id: number,
readonly title: string, readonly title: string,
) { ) {
@ -10,6 +11,7 @@ export class SearchResult {
static fromJson(json: any): SearchResult { static fromJson(json: any): SearchResult {
return new SearchResult( return new SearchResult(
validateStringNotEmptyNotNull(json['type']),
validateNumberNotNull(json['id']), validateNumberNotNull(json['id']),
validateStringNotEmptyNotNull(json['title']), validateStringNotEmptyNotNull(json['title']),
); );

View File

@ -7,6 +7,7 @@ import {CompatClient, Stomp} from "@stomp/stompjs";
import {Update} from "./Update"; import {Update} from "./Update";
import {LocationStrategy} from "@angular/common"; import {LocationStrategy} from "@angular/common";
import {Page} from "./Page"; import {Page} from "./Page";
import {Next} from "./types";
export function NO_OP() { 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)); 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 { private restUrl(path: string): string {
return environment.restBase + this.locationStrategy.getBaseHref() + path; return environment.restBase + this.locationStrategy.getBaseHref() + path;
} }

View File

@ -8,7 +8,7 @@ export class BulkEntry {
readonly version: number, readonly version: number,
readonly position: number, readonly position: number,
readonly enabled: boolean, readonly enabled: boolean,
readonly property: Property, readonly property: Property | null,
readonly value: number, readonly value: number,
) { ) {
// nothing // nothing
@ -20,7 +20,7 @@ export class BulkEntry {
validateNumberNotNull(json['version']), validateNumberNotNull(json['version']),
validateNumberNotNull(json['position']), validateNumberNotNull(json['position']),
validateBooleanNotNull(json['enabled']), validateBooleanNotNull(json['enabled']),
Property.fromJson(json['property']), Property.fromJsonOrNull(json['property']),
validateNumberNotNull(json['value']), 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 {Update} from "../Update";
import {Bulk} from "./Bulk"; import {Bulk} from "./Bulk";
import {BulkEntry} from "./BulkEntry"; import {BulkEntry} from "./BulkEntry";
import {Next} from "../types";
import {BulkEntryCreate} from "./BulkEntryCreate";
class BulkCreate {
constructor(
readonly name: string,
readonly enabled: boolean,
) {
// nothing
}
}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -42,7 +55,7 @@ export class BulkService implements ISearchService {
} }
create(next: (item: Bulk) => void, error: (error: any) => void = NO_OP): void { 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 { 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); 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 {Channel} from "../channel/Channel";
import {SearchResult} from "../SearchResult";
export class Property { export class Property {
@ -11,6 +12,7 @@ export class Property {
public timestamp: Date | null, public timestamp: Date | null,
public readChannel: Channel | null, public readChannel: Channel | null,
public writeChannel: Channel | null, public writeChannel: Channel | null,
public usages: SearchResult[],
) { ) {
// nothing // nothing
} }
@ -31,9 +33,17 @@ export class Property {
validateDateAllowNull(json['timestamp']), validateDateAllowNull(json['timestamp']),
Channel.fromJsonAllowNull(json['readChannel']), Channel.fromJsonAllowNull(json['readChannel']),
Channel.fromJsonAllowNull(json['writeChannel']), 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 { public static trackBy(index: number, item: Property): number {
return item.id; return item.id;
} }

View File

@ -4,6 +4,7 @@ import {ISearchService} from "../ISearchService";
import {SearchResult} from "../SearchResult"; import {SearchResult} from "../SearchResult";
import {Update} from "../Update"; import {Update} from "../Update";
import {Property} from "./Property"; import {Property} from "./Property";
import {Next} from "../types";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -40,4 +41,8 @@ export class PropertyService implements ISearchService {
this.api.getReturnItem("property/create/", Property.fromJson, next, error); 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,7 +1,10 @@
<table> <ng-container *ngIf="bulk">
<h1>{{bulk.name}}</h1>
<table>
<tr> <tr>
<th>Eigenschaft</th> <th>Eigenschaft</th>
<th>Wert</th> <th>Wert</th>
<th>&nbsp;</th>
</tr> </tr>
<ng-container *ngIf="bulk"> <ng-container *ngIf="bulk">
<tr *ngFor="let entry of bulk.entries"> <tr *ngFor="let entry of bulk.entries">
@ -9,6 +12,7 @@
<app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search> <app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search>
</td> </td>
<ng-container [ngSwitch]="entry.property?.type"> <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)"> <td *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="setEntry(entry, 'value', entry.value > 0 ? 0 : 1)">
{{entry.value ? "An" : "Aus"}} {{entry.value ? "An" : "Aus"}}
</td> </td>
@ -40,7 +44,17 @@
<td *ngSwitchDefault class="empty"> <td *ngSwitchDefault class="empty">
&nbsp; &nbsp;
</td> </td>
<td class="delete">
<fa-icon title="Löschen" [icon]="faTimes" (click)="delete(entry)"></fa-icon>
</td>
</ng-container> </ng-container>
</tr> </tr>
</ng-container> </ng-container>
</table> </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 {ActivatedRoute} from "@angular/router";
import {PropertyService} from "../../../api/property/property.service"; import {PropertyService} from "../../../api/property/property.service";
import {BulkEntry} from "../../../api/bulk/BulkEntry"; import {BulkEntry} from "../../../api/bulk/BulkEntry";
import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
@Component({ @Component({
selector: 'app-bulk', selector: 'app-bulk',
@ -12,6 +13,8 @@ import {BulkEntry} from "../../../api/bulk/BulkEntry";
}) })
export class BulkComponent implements OnInit { export class BulkComponent implements OnInit {
readonly faTimes = faTimesCircle;
bulk!: Bulk; bulk!: Bulk;
constructor( 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>Zeitstempel</th>
<th>Lesekanal</th> <th>Lesekanal</th>
<th>Schreibkanal</th> <th>Schreibkanal</th>
<th>
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
</th>
</tr> </tr>
<ng-container *ngFor="let property of properties.sort(Property.compareTypeThenTitle)"> <ng-container *ngFor="let property of properties.sort(Property.compareTypeThenTitle)">
<tr> <tr>
<td> <td>
<app-edit-field [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-edit-field> <app-edit-field [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-edit-field>
</td> </td>
<td> <td>
<select [(ngModel)]="property.type" (ngModelChange)="edit(property, 'type', property.type)"> <select [(ngModel)]="property.type" (ngModelChange)="edit(property, 'type', property.type)">
<option value="BOOLEAN">Schalter</option> <option value="BOOLEAN">Schalter</option>
@ -30,6 +35,7 @@
<option value="SCENE">Szene</option> <option value="SCENE">Szene</option>
</select> </select>
</td> </td>
<ng-container *ngIf="property.value !== null else empty"> <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)"> <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"}} {{property.value ? "An" : "Aus"}}
@ -50,15 +56,22 @@
{{findScene(property)?.title || "Unbekannt: " + property.value}} {{findScene(property)?.title || "Unbekannt: " + property.value}}
</td> </td>
</ng-container> </ng-container>
<td *ngIf="property.timestamp !== null else empty"> <td *ngIf="property.timestamp !== null else empty">
{{property.timestamp | date:'yyyy-MM-dd HH:mm:ss'}} {{property.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}
</td> </td>
<td class="full"> <td class="full">
<app-search [searchService]="channelService" [initial]="property.readChannel?.id" (valueChange)="set(property, 'readChannel', $event)"></app-search> <app-search [searchService]="channelService" [initial]="property.readChannel?.id" (valueChange)="set(property, 'readChannel', $event)"></app-search>
</td> </td>
<td class="full"> <td class="full">
<app-search [searchService]="channelService" [initial]="property.writeChannel?.id" (valueChange)="set(property, 'writeChannel', $event)"></app-search> <app-search [searchService]="channelService" [initial]="property.writeChannel?.id" (valueChange)="set(property, 'writeChannel', $event)"></app-search>
</td> </td>
<td class="delete">
<fa-icon title="Löschen" *ngIf="property.usages.length === 0" [icon]="faTimes" (click)="delete(property)"></fa-icon>
</td>
</tr> </tr>
</ng-container> </ng-container>
</table> </table>

View File

@ -4,6 +4,7 @@ import {PropertyService} from "../../api/property/property.service";
import {Scene} from "../../api/scene/Scene"; import {Scene} from "../../api/scene/Scene";
import {SceneService} from "../../api/scene/scene.service"; import {SceneService} from "../../api/scene/scene.service";
import {ChannelService} from "../../api/channel/channel.service"; import {ChannelService} from "../../api/channel/channel.service";
import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
@Component({ @Component({
selector: 'app-property-list', selector: 'app-property-list',
@ -12,6 +13,8 @@ import {ChannelService} from "../../api/channel/channel.service";
}) })
export class PropertyListComponent implements OnInit { export class PropertyListComponent implements OnInit {
readonly faTimes = faTimesCircle;
Property = Property; Property = Property;
properties: Property[] = []; properties: Property[] = [];
@ -80,4 +83,10 @@ export class PropertyListComponent implements OnInit {
this.propertyService.create(property => this.updateProperty(property, true)); 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 { .delete {
color: darkred; color: darkred;
text-align: center;
} }
.disabled { .disabled {

View File

@ -18,6 +18,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.ZonedDateTime;
@SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"}) @SuppressWarnings({"unchecked", "UnusedReturnValue", "SameParameterValue", "UnusedAssignment", "RedundantSuppression"})
@Slf4j @Slf4j
@Service @Service
@ -52,7 +54,8 @@ public class DemoDataService {
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBrightness.getId(), 40, 0)); bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBrightness.getId(), 40, 0));
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkColorTemperature.getId(), 55, 0)); bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkColorTemperature.getId(), 55, 0));
final long scheduleId = createSchedule(true, "schedule"); 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) { 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) { 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; private final PropertyWriteService propertyWriteService;
public void execute(final Bulk bulk) { 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())); bulk.getEntries().forEach(entry -> propertyWriteService.writeToChannel(entry.getProperty(), entry.getValue()));
log.debug("Finished executing Bulk: {}", bulk); log.debug("Finished executing Bulk: {}", bulk);
} }

View File

@ -34,7 +34,7 @@ public class BulkMapper {
} }
public BulkEntryDto toDto(final BulkEntry entry) { 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); return new BulkEntryDto(entry, property);
} }

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.bulk; package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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)); 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; package de.ph87.homeautomation.bulk;
import de.ph87.homeautomation.property.Property;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 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); Optional<Bulk> findByEntries_Id(long id);
List<Bulk> findDistinctByEntries_Property(Property property);
} }

View File

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

View File

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

View File

@ -56,7 +56,9 @@ public class BulkEntryWriter {
} }
public void remove(final long id) { 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) { private BulkEntry getEntryById(final long id) {

View File

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

View File

@ -1,11 +1,20 @@
package de.ph87.homeautomation.property; 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.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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@Transactional @Transactional
@ -14,14 +23,27 @@ public class PropertyMapper {
private final ChannelService channelService; private final ChannelService channelService;
private final ScheduleRepository scheduleRepository;
private final BulkRepository bulkRepository;
public PropertyDto toDto(final Property property) { public PropertyDto toDto(final Property property) {
final List<SearchResult> usages = findUsages(property);
return new PropertyDto( return new PropertyDto(
property, property,
channelService.toDtoAllowNull(property.getReadChannel()), 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) { public PropertyDto toDtoOrNull(final Property property) {
if (property == null) { if (property == null) {
return null; return null;
@ -29,4 +51,12 @@ public class PropertyMapper {
return toDto(property); 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; private String title;
@ToString.Exclude @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<>(); private Set<ScheduleEntry> entries = new HashSet<>();
} }

View File

@ -29,14 +29,14 @@ public class ScheduleCalculationService {
private final Config config; private final Config config;
private final ScheduleReadService scheduleReadService; private final ScheduleReader scheduleReader;
private final ApplicationEventPublisher applicationEventPublisher; private final ApplicationEventPublisher applicationEventPublisher;
@EventListener(ApplicationStartedEvent.class) @EventListener(ApplicationStartedEvent.class)
public void calculateAllNext() { public void calculateAllNext() {
final ZonedDateTime now = ZonedDateTime.now(); 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) { public void calculateSchedule(final Schedule schedule, final ZonedDateTime now) {

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package de.ph87.homeautomation.schedule; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import de.ph87.homeautomation.web.NotFoundException; import de.ph87.homeautomation.web.NotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -14,7 +15,7 @@ import java.util.stream.Collectors;
@Service @Service
@Transactional @Transactional
@RequiredArgsConstructor @RequiredArgsConstructor
public class ScheduleReadService { public class ScheduleReader {
private final ScheduleRepository scheduleRepository; private final ScheduleRepository scheduleRepository;
@ -40,4 +41,8 @@ public class ScheduleReadService {
return scheduleMapper.toDto(getById(id)); 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; package de.ph87.homeautomation.schedule;
import de.ph87.homeautomation.property.Property;
import de.ph87.homeautomation.schedule.entry.ScheduleEntry; import de.ph87.homeautomation.schedule.entry.ScheduleEntry;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
@ -13,4 +14,6 @@ public interface ScheduleRepository extends CrudRepository<Schedule, Long> {
boolean existsByTitle(String title); boolean existsByTitle(String title);
List<Schedule> findDistinctByEntries_Property(Property property);
} }

View File

@ -1,6 +1,6 @@
package de.ph87.homeautomation.schedule; 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 de.ph87.homeautomation.shared.AbstractThreadService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -17,7 +17,7 @@ public class ScheduleThreadService extends AbstractThreadService {
private final ScheduleExecutionService scheduleExecutionService; private final ScheduleExecutionService scheduleExecutionService;
private final ScheduleEntryReadService scheduleEntryReadService; private final ScheduleEntryReader scheduleEntryReader;
@Override @Override
protected String getThreadName() { protected String getThreadName() {
@ -32,7 +32,7 @@ public class ScheduleThreadService extends AbstractThreadService {
@Override @Override
protected long doStep() throws InterruptedException { protected long doStep() throws InterruptedException {
scheduleExecutionService.executeAllLastDue(); 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 @Override

View File

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

View File

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

View File

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

View File

@ -5,11 +5,14 @@ import lombok.Getter;
@Getter @Getter
public class SearchResult { 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.id = id;
this.title = title; this.title = title;
} }