TIME-domain foundation - Leitstand
This commit is contained in:
parent
8b22e96597
commit
9b0777e6ce
|
|
@ -0,0 +1,23 @@
|
|||
# TIME Phase T2 Leitstand Import
|
||||
|
||||
This phase adds the NEW-only Leitstand source import layer on top of the T1 TIME foundation.
|
||||
|
||||
## Included
|
||||
|
||||
- source-specific import tables in `TIME` schema (`ls_*`)
|
||||
- NEW-only external JDBC configuration for Leitstand
|
||||
- source client for the provided SQL Server 2000 tables
|
||||
- startup sync runner, disabled by default
|
||||
- importer that:
|
||||
- upserts Leitstand source rows into `TIME.ls_*`
|
||||
- creates/updates canonical `TIME.time_entry` + `DOC.doc_document` roots from `E_90716`
|
||||
- writes source links for time recording, person, and activity type
|
||||
- updates `TIME.time_sync_run` and `TIME.time_sync_state`
|
||||
|
||||
## Not included yet
|
||||
|
||||
- search projection
|
||||
- text representations
|
||||
- embeddings
|
||||
- structured search
|
||||
- Toggl import
|
||||
23
pom.xml
23
pom.xml
|
|
@ -207,6 +207,29 @@
|
|||
<version>2.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>sqljdbc4</artifactId>
|
||||
<version>4.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jtds</groupId>
|
||||
<artifactId>jtds</artifactId>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
|||
|
|
@ -4,17 +4,13 @@ import lombok.Data;
|
|||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* NEW-runtime configuration scaffold for the TIME domain.
|
||||
* T1 only introduces schema and persistence foundations; source import starts in T2.
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "dip.time")
|
||||
@Data
|
||||
public class TimeDomainProperties {
|
||||
|
||||
private boolean enabled = false;
|
||||
private SourceProperties leitstand = new SourceProperties();
|
||||
private LeitstandProperties leitstand = new LeitstandProperties();
|
||||
private SourceProperties togglTrack = new SourceProperties();
|
||||
|
||||
@Data
|
||||
|
|
@ -23,4 +19,23 @@ public class TimeDomainProperties {
|
|||
private String importBatchId;
|
||||
private int reconcileLookbackDays = 7;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LeitstandProperties extends SourceProperties {
|
||||
private boolean startupSyncEnabled = false;
|
||||
private boolean createCanonicalTimeEntries = true;
|
||||
private boolean incrementalEnabled = true;
|
||||
private String scopeKey = "leitstand-default";
|
||||
private JdbcProperties jdbc = new JdbcProperties();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class JdbcProperties {
|
||||
private String url;
|
||||
private String username;
|
||||
private String password;
|
||||
private String driverClassName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
|
||||
private int fetchSize = 500;
|
||||
private int queryTimeoutSeconds = 300;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import lombok.Builder;
|
|||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
* Shared canonical time-entry fact that can be sourced from multiple external systems.
|
||||
|
|
@ -51,7 +53,8 @@ public class TimeEntry {
|
|||
private Document document;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "source_system", nullable = false, length = 32)
|
||||
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
|
||||
@Column(name = "source_system", columnDefinition = "TIME.time_source_system")
|
||||
private TimeSourceSystem sourceSystem;
|
||||
|
||||
@Column(name = "external_id", nullable = false, length = 255)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import lombok.Builder;
|
|||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
* Reverse-link helper that maps a canonical time entry to one or more source-system entity identifiers.
|
||||
|
|
@ -48,7 +50,8 @@ public class TimeEntrySourceLink {
|
|||
private TimeEntry timeEntry;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "source_system", nullable = false, length = 32)
|
||||
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
|
||||
@Column(name = "source_system", columnDefinition = "TIME.time_source_system")
|
||||
private TimeSourceSystem sourceSystem;
|
||||
|
||||
@Column(name = "source_entity_type", nullable = false, length = 120)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import lombok.Builder;
|
|||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
* Audit row for one synchronization run of a time-entry source system.
|
||||
|
|
@ -42,18 +44,21 @@ public class TimeSyncRun {
|
|||
private UUID id;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "source_system", nullable = false, length = 32)
|
||||
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
|
||||
@Column(name = "source_system", columnDefinition = "TIME.time_source_system")
|
||||
private TimeSourceSystem sourceSystem;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "run_type", nullable = false, length = 32)
|
||||
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
|
||||
@Column(name = "run_type", columnDefinition = "TIME.time_sync_run_system")
|
||||
private TimeSyncRunType runType;
|
||||
|
||||
@Column(name = "scope_key", nullable = false, length = 255)
|
||||
private String scopeKey;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 32)
|
||||
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
|
||||
@Column(name = "status", columnDefinition = "TIME.time_sync_run_status")
|
||||
private TimeSyncRunStatus status;
|
||||
|
||||
@Builder.Default
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import lombok.Builder;
|
|||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
* Mutable synchronization cursor/state per source system and scope.
|
||||
|
|
@ -43,7 +45,8 @@ public class TimeSyncState {
|
|||
private UUID id;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "source_system", nullable = false, length = 32)
|
||||
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
|
||||
@Column(name = "source_system", columnDefinition = "TIME.time_source_system")
|
||||
private TimeSourceSystem sourceSystem;
|
||||
|
||||
@Column(name = "scope_key", nullable = false, length = 255)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@MappedSuperclass
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractLeitstandDbkEntity extends AbstractLeitstandStampedEntity {
|
||||
@Id
|
||||
@Column(name = "dbk", nullable = false, length = 24)
|
||||
private String dbk;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@MappedSuperclass
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractLeitstandStampedEntity {
|
||||
@Column(name = "dbk_uid", length = 7) private String dbkUid;
|
||||
@Column(name = "upd", length = 24) private String upd;
|
||||
@Column(name = "upd_anz") private Integer updAnz;
|
||||
@Column(name = "upd_uid", length = 7) private String updUid;
|
||||
@Column(name = "status") private Integer status;
|
||||
@Column(name = "created_at", nullable = false, updatable = false) private OffsetDateTime createdAt = OffsetDateTime.now();
|
||||
@Column(name = "updated_at", nullable = false) private OffsetDateTime updatedAt = OffsetDateTime.now();
|
||||
@PrePersist protected void onCreate() { createdAt = OffsetDateTime.now(); updatedAt = OffsetDateTime.now(); }
|
||||
@PreUpdate protected void onUpdate() { updatedAt = OffsetDateTime.now(); }
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_activity_type", indexes = {
|
||||
@Index(name = "idx_ls_activity_type_code", columnList = "l_code")
|
||||
})
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class LeitstandActivityType {
|
||||
@Id @Column(name = "id", nullable = false) private Integer id;
|
||||
@Column(name = "l_code", length = 32) private String lCode;
|
||||
@Column(name = "bez", length = 255) private String bez;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_contract", indexes = {
|
||||
@Index(name = "idx_ls_contract_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_contract_org_dbk", columnList = "organization_dbk"),
|
||||
@Index(name = "idx_ls_contract_name", columnList = "name"),
|
||||
@Index(name = "idx_ls_contract_iref", columnList = "iref")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandContract extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "organization_dbk", length = 24) private String organizationDbk;
|
||||
@Column(name = "lfdnr") private Integer lfdnr;
|
||||
@Column(name = "name", length = 255) private String name;
|
||||
@Column(name = "iref", length = 255) private String iref;
|
||||
@Column(name = "eref", length = 255) private String eref;
|
||||
@Column(name = "description", length = 255) private String description;
|
||||
@Column(name = "valid_from") private OffsetDateTime validFrom;
|
||||
@Column(name = "valid_to") private OffsetDateTime validTo;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_contract_position", indexes = {
|
||||
@Index(name = "idx_ls_contract_position_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_contract_position_contract_dbk", columnList = "contract_dbk"),
|
||||
@Index(name = "idx_ls_contract_position_name", columnList = "name"),
|
||||
@Index(name = "idx_ls_contract_position_iref", columnList = "iref")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandContractPosition extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "contract_dbk", length = 24) private String contractDbk;
|
||||
@Column(name = "lfdnr") private Integer lfdnr;
|
||||
@Column(name = "project_name", length = 255) private String projectName;
|
||||
@Column(name = "sales_price", precision = 18, scale = 4) private BigDecimal salesPrice;
|
||||
@Column(name = "purchase_price", precision = 18, scale = 4) private BigDecimal purchasePrice;
|
||||
@Column(name = "description", length = 255) private String description;
|
||||
@Column(name = "valid_from") private OffsetDateTime validFrom;
|
||||
@Column(name = "valid_to") private OffsetDateTime validTo;
|
||||
@Column(name = "name", length = 255) private String name;
|
||||
@Column(name = "iref", length = 255) private String iref;
|
||||
@Column(name = "eref", length = 255) private String eref;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_cost_unit", indexes = {
|
||||
@Index(name = "idx_ls_cost_unit_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_cost_unit_mcl_id", columnList = "mcl_id"),
|
||||
@Index(name = "idx_ls_cost_unit_person_dbk", columnList = "person_dbk"),
|
||||
@Index(name = "idx_ls_cost_unit_org_dbk", columnList = "organization_dbk"),
|
||||
@Index(name = "idx_ls_cost_unit_contract_dbk", columnList = "contract_dbk"),
|
||||
@Index(name = "idx_ls_cost_unit_contract_position_dbk", columnList = "contract_position_dbk")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandCostUnit extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "person_dbk", length = 24) private String personDbk;
|
||||
@Column(name = "organization_dbk", length = 24) private String organizationDbk;
|
||||
@Column(name = "contract_dbk", length = 24) private String contractDbk;
|
||||
@Column(name = "legacy_relation_4_dbk", length = 24) private String legacyRelation4Dbk;
|
||||
@Column(name = "legacy_relation_5_dbk", length = 24) private String legacyRelation5Dbk;
|
||||
@Column(name = "contract_position_dbk", length = 24) private String contractPositionDbk;
|
||||
@Column(name = "project_name", length = 255) private String projectName;
|
||||
@Column(name = "project_id") private Integer projectId;
|
||||
@Column(name = "project_task") private Integer projectTask;
|
||||
@Column(name = "mcl_id", length = 255) private String mclId;
|
||||
@Column(name = "mcl_name", length = 255) private String mclName;
|
||||
@Column(name = "mcl_desc", length = 255) private String mclDesc;
|
||||
@Column(name = "valid_from") private OffsetDateTime validFrom;
|
||||
@Column(name = "valid_to") private OffsetDateTime validTo;
|
||||
@Column(name = "effort_plan", precision = 19, scale = 3) private BigDecimal effortPlan;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_organization", indexes = {
|
||||
@Index(name = "idx_ls_organization_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_organization_name", columnList = "name"),
|
||||
@Index(name = "idx_ls_organization_org_nr", columnList = "org_nr")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandOrganization extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "betreu", length = 2) private String betreu;
|
||||
@Column(name = "name", length = 255) private String name;
|
||||
@Column(name = "street", length = 50) private String street;
|
||||
@Column(name = "postal_code", length = 6) private String postalCode;
|
||||
@Column(name = "city", length = 50) private String city;
|
||||
@Column(name = "phone", length = 30) private String phone;
|
||||
@Column(name = "fax", length = 30) private String fax;
|
||||
@Column(name = "email", length = 50) private String email;
|
||||
@Column(name = "org_nr") private Integer orgNumber;
|
||||
@Column(name = "short_name", length = 30) private String shortName;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_person", indexes = {
|
||||
@Index(name = "idx_ls_person_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_person_person_nr", columnList = "pers_nr"),
|
||||
@Index(name = "idx_ls_person_org_ref", columnList = "organization_dbk")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandPerson extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "pers_nr") private Integer personNumber;
|
||||
@Column(name = "last_name", length = 35) private String lastName;
|
||||
@Column(name = "first_name", length = 25) private String firstName;
|
||||
@Column(name = "category_dbk", length = 24) private String categoryDbk;
|
||||
@Column(name = "salutation", length = 15) private String salutation;
|
||||
@Column(name = "title", length = 30) private String title;
|
||||
@Column(name = "street", length = 36) private String street;
|
||||
@Column(name = "postal_code", length = 6) private String postalCode;
|
||||
@Column(name = "city", length = 30) private String city;
|
||||
@Column(name = "country_code", length = 4) private String countryCode;
|
||||
@Column(name = "phone", length = 30) private String phone;
|
||||
@Column(name = "fax", length = 30) private String fax;
|
||||
@Column(name = "email", length = 50) private String email;
|
||||
@Column(name = "cost_per_hour", precision = 8, scale = 3) private BigDecimal costPerHour;
|
||||
@Column(name = "organization_dbk", length = 24) private String organizationDbk;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_person_task_assignment", indexes = {
|
||||
@Index(name = "idx_ls_person_task_assignment_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_person_task_assignment_task_dbk", columnList = "task_dbk"),
|
||||
@Index(name = "idx_ls_person_task_assignment_person_dbk", columnList = "person_dbk"),
|
||||
@Index(name = "idx_ls_person_task_assignment_cost_unit_dbk", columnList = "cost_unit_dbk")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandPersonTaskAssignment extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "task_dbk", length = 24) private String taskDbk;
|
||||
@Column(name = "person_dbk", length = 24) private String personDbk;
|
||||
@Column(name = "cost_unit_dbk", length = 24) private String costUnitDbk;
|
||||
@Column(name = "rtype", length = 1) private String rtype;
|
||||
@Column(name = "last_work") private OffsetDateTime lastWork;
|
||||
@Column(name = "effort_plan", precision = 19, scale = 3) private BigDecimal effortPlan;
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_task", indexes = {
|
||||
@Index(name = "idx_ls_task_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_task_mcl_id", columnList = "mcl_id"),
|
||||
@Index(name = "idx_ls_task_primary_cost_unit_dbk", columnList = "primary_cost_unit_dbk"),
|
||||
@Index(name = "idx_ls_task_parent_task_dbk", columnList = "parent_task_dbk")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandTask extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "primary_cost_unit_dbk", length = 24) private String primaryCostUnitDbk;
|
||||
@Column(name = "secondary_cost_unit_dbk", length = 24) private String secondaryCostUnitDbk;
|
||||
@Column(name = "tertiary_cost_unit_dbk", length = 24) private String tertiaryCostUnitDbk;
|
||||
@Column(name = "parent_task_dbk", length = 24) private String parentTaskDbk;
|
||||
@Column(name = "lfdnr") private Integer lfdnr;
|
||||
@Column(name = "task_type", length = 1) private String taskType;
|
||||
@Column(name = "mcl_id", length = 255) private String mclId;
|
||||
@Column(name = "mcl_name", length = 255) private String mclName;
|
||||
@Column(name = "mcl_desc", length = 8000) private String mclDesc;
|
||||
@Column(name = "valid_from") private OffsetDateTime validFrom;
|
||||
@Column(name = "valid_to") private OffsetDateTime validTo;
|
||||
@Column(name = "effort_plan", precision = 19, scale = 1) private BigDecimal effortPlan;
|
||||
@Column(name = "last_work") private OffsetDateTime lastWork;
|
||||
@Column(name = "completion_percent") private Integer completionPercent;
|
||||
@Column(name = "done_date") private OffsetDateTime doneDate;
|
||||
@Column(name = "remark", length = 255) private String remark;
|
||||
@Column(name = "n_rid") private Integer nRid;
|
||||
@Column(name = "legacy_90702_rid") private Integer legacy90702Rid;
|
||||
@Column(name = "amount", precision = 18, scale = 4) private BigDecimal amount;
|
||||
@Column(name = "created_source_at") private OffsetDateTime createdSourceAt;
|
||||
@Column(name = "legacy_90700_rid") private Integer legacy90700Rid;
|
||||
@Column(name = "legacy_90699_rid") private Integer legacy90699Rid;
|
||||
@Column(name = "quantity") private Integer quantity;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import at.procon.dip.domain.time.entity.TimeEntry;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_time_recording", indexes = {
|
||||
@Index(name = "idx_ls_time_recording_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_time_recording_person_dbk", columnList = "person_dbk"),
|
||||
@Index(name = "idx_ls_time_recording_activity_type_id", columnList = "activity_type_id"),
|
||||
@Index(name = "idx_ls_time_recording_from", columnList = "recorded_from"),
|
||||
@Index(name = "idx_ls_time_recording_to", columnList = "recorded_to")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandTimeRecording extends AbstractLeitstandDbkEntity {
|
||||
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "time_entry_id", unique = true) private TimeEntry timeEntry;
|
||||
@Column(name = "person_dbk", length = 24) private String personDbk;
|
||||
@Column(name = "lfdnr") private Integer lfdnr;
|
||||
@Column(name = "record_type", length = 32) private String recordType;
|
||||
@Column(name = "mcl_id", columnDefinition = "TEXT") private String mclId;
|
||||
@Column(name = "mcl_desc", columnDefinition = "TEXT") private String mclDesc;
|
||||
@Column(name = "recorded_from") private OffsetDateTime recordedFrom;
|
||||
@Column(name = "recorded_to") private OffsetDateTime recordedTo;
|
||||
@Column(name = "effort", precision = 10, scale = 3) private BigDecimal effort;
|
||||
@Column(name = "remark", columnDefinition = "TEXT") private String remark;
|
||||
@Column(name = "url", length = 1000) private String url;
|
||||
@Column(name = "activity_type_id") private Integer activityTypeId;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package at.procon.dip.domain.time.entity.leitstand;
|
||||
|
||||
import at.procon.dip.architecture.SchemaNames;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(schema = SchemaNames.TIME, name = "ls_time_recording_assignment", indexes = {
|
||||
@Index(name = "idx_ls_time_recording_assignment_upd", columnList = "upd"),
|
||||
@Index(name = "idx_ls_time_recording_assignment_person_task_assignment_dbk", columnList = "person_task_assignment_dbk"),
|
||||
@Index(name = "idx_ls_time_recording_assignment_time_recording_dbk", columnList = "time_recording_dbk")
|
||||
})
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class LeitstandTimeRecordingAssignment extends AbstractLeitstandDbkEntity {
|
||||
@Column(name = "person_task_assignment_dbk", length = 24) private String personTaskAssignmentDbk;
|
||||
@Column(name = "time_recording_dbk", length = 24) private String timeRecordingDbk;
|
||||
@Column(name = "mcl_desc", columnDefinition = "TEXT") private String mclDesc;
|
||||
@Column(name = "effort", precision = 19, scale = 3) private BigDecimal effort;
|
||||
@Column(name = "remark", columnDefinition = "TEXT") private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package at.procon.dip.domain.time.leitstand.config;
|
||||
|
||||
import at.procon.dip.domain.time.config.TimeDomainProperties;
|
||||
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
|
||||
import at.procon.dip.runtime.config.RuntimeMode;
|
||||
import javax.sql.DataSource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
|
||||
@ConditionalOnProperty(prefix = "dip.time.leitstand", name = "enabled", havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
public class LeitstandTimeImportConfiguration {
|
||||
|
||||
private final TimeDomainProperties properties;
|
||||
|
||||
|
||||
private DataSource createLeitstandDataSource() {
|
||||
var jdbc = properties.getLeitstand().getJdbc();
|
||||
DriverManagerDataSource ds = new DriverManagerDataSource();
|
||||
ds.setDriverClassName(jdbc.getDriverClassName());
|
||||
ds.setUrl(jdbc.getUrl());
|
||||
ds.setUsername(jdbc.getUsername());
|
||||
ds.setPassword(jdbc.getPassword());
|
||||
return ds;
|
||||
}
|
||||
|
||||
@Bean(name = "applicationJdbcTemplate")
|
||||
public JdbcTemplate applicationJdbcTemplate(DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Bean(name = "leitstandTimeNamedParameterJdbcTemplate")
|
||||
public NamedParameterJdbcTemplate leitstandNamedParameterJdbcTemplate() {
|
||||
return new NamedParameterJdbcTemplate(createLeitstandDataSource());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package at.procon.dip.domain.time.leitstand.source;
|
||||
|
||||
import at.procon.dip.domain.time.config.TimeDomainProperties;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class JdbcLeitstandTimeSourceClient implements LeitstandTimeSourceClient {
|
||||
|
||||
private final TimeDomainProperties properties;
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
public JdbcLeitstandTimeSourceClient(TimeDomainProperties properties,
|
||||
@Qualifier("leitstandTimeNamedParameterJdbcTemplate") NamedParameterJdbcTemplate jdbcTemplate) {
|
||||
this.properties = properties;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.ActivityTypeRow> fetchActivityTypes() {
|
||||
return jdbcTemplate.query("select ID, L_CODE, BEZ from E_90735 order by ID", Map.of(),
|
||||
(rs, n) -> new LeitstandSourceRows.ActivityTypeRow(intVal(rs, "ID"), trim(rs.getString("L_CODE")), trim(rs.getString("BEZ"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.PersonRow> fetchPersons(String watermark) {
|
||||
return queryByWatermark("E_10009", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, PERS_NR, NAME, VORNAME, REKEY01, ANREDE, TITEL, STRASSE, PLZ, ORT, LANDCODE, TEL_NR, FAX_NR, E_MAIL, MCL_COSTPH, N_REKEY02 from E_10009", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.PersonRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), intVal(rs, "PERS_NR"), trim(rs.getString("NAME")), trim(rs.getString("VORNAME")), trim(rs.getString("REKEY01")), trim(rs.getString("ANREDE")), trim(rs.getString("TITEL")), trim(rs.getString("STRASSE")), trim(rs.getString("PLZ")), trim(rs.getString("ORT")), trim(rs.getString("LANDCODE")), trim(rs.getString("TEL_NR")), trim(rs.getString("FAX_NR")), trim(rs.getString("E_MAIL")), dec(rs, "MCL_COSTPH"), trim(rs.getString("N_REKEY02"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.OrganizationRow> fetchOrganizations(String watermark) {
|
||||
return queryByWatermark("E_10072", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, BETREU, NAME, STRASSE, PLZ, ORT, TEL_NR, FAX_NR, E_MAIL, ORG_NR, org_kurz from E_10072", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.OrganizationRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("BETREU")), trim(rs.getString("NAME")), trim(rs.getString("STRASSE")), trim(rs.getString("PLZ")), trim(rs.getString("ORT")), trim(rs.getString("TEL_NR")), trim(rs.getString("FAX_NR")), trim(rs.getString("E_MAIL")), intVal(rs, "ORG_NR"), trim(rs.getString("org_kurz"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.ContractRow> fetchContracts(String watermark) {
|
||||
return queryByWatermark("E_90711", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, REKEY01, LFDNR, NAME, IREF, EREF, MCL_DESC, MCL_FROM, MCL_TO from E_90711", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.ContractRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("REKEY01")), intVal(rs, "LFDNR"), trim(rs.getString("NAME")), trim(rs.getString("IREF")), trim(rs.getString("EREF")), trim(rs.getString("MCL_DESC")), ts(rs, "MCL_FROM"), ts(rs, "MCL_TO")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.ContractPositionRow> fetchContractPositions(String watermark) {
|
||||
return queryByWatermark("E_90712", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, REKEY01, LFDNR, PROJECTNAME, VK_PREIS, EK_PREIS, MCL_DESC, MCL_FROM, MCL_TO, NAME, IREF, EREF from E_90712", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.ContractPositionRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("REKEY01")), intVal(rs, "LFDNR"), trim(rs.getString("PROJECTNAME")), dec(rs, "VK_PREIS"), dec(rs, "EK_PREIS"), trim(rs.getString("MCL_DESC")), ts(rs, "MCL_FROM"), ts(rs, "MCL_TO"), trim(rs.getString("NAME")), trim(rs.getString("IREF")), trim(rs.getString("EREF"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.CostUnitRow> fetchCostUnits(String watermark) {
|
||||
return queryByWatermark("E_90705", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, N_REKEY01, N_REKEY02, N_REKEY03, N_REKEY04, N_REKEY05, N_REKEY06, PROJECT_NAME, PROJECT_ID, PROJECT_TASK, MCL_ID, MCL_NAME, MCL_DESC, MCL_FROM, MCL_TO, MCL_EFFORT_PLAN from E_90705", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.CostUnitRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("N_REKEY01")), trim(rs.getString("N_REKEY02")), trim(rs.getString("N_REKEY03")), trim(rs.getString("N_REKEY04")), trim(rs.getString("N_REKEY05")), trim(rs.getString("N_REKEY06")), trim(rs.getString("PROJECT_NAME")), intVal(rs, "PROJECT_ID"), intVal(rs, "PROJECT_TASK"), trim(rs.getString("MCL_ID")), trim(rs.getString("MCL_NAME")), trim(rs.getString("MCL_DESC")), ts(rs, "MCL_FROM"), ts(rs, "MCL_TO"), dec(rs, "MCL_EFFORT_PLAN")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.TaskRow> fetchTasks(String watermark) {
|
||||
return queryByWatermark("E_90708", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, REKEY01, REKEY02, REKEY03, N_REKEY04, LFDNR, TASK_TYPE, MCL_ID, MCL_NAME, MCL_DESC, MCL_FROM, MCL_TO, MCL_EFFORT_PLAN, MCL_LASTWORK, MCL_COMPLETE, MCL_DONE_DATE, MCL_REMARK, N_RID, E_90702_RID, BETRAG, CR_DATE, E_90700_RID, E_90699_RID, ANZAHL from E_90708", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.TaskRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("REKEY01")), trim(rs.getString("REKEY02")), trim(rs.getString("REKEY03")), trim(rs.getString("N_REKEY04")), intVal(rs, "LFDNR"), trim(rs.getString("TASK_TYPE")), trim(rs.getString("MCL_ID")), trim(rs.getString("MCL_NAME")), trim(rs.getString("MCL_DESC")), ts(rs, "MCL_FROM"), ts(rs, "MCL_TO"), dec(rs, "MCL_EFFORT_PLAN"), ts(rs, "MCL_LASTWORK"), intVal(rs, "MCL_COMPLETE"), ts(rs, "MCL_DONE_DATE"), trim(rs.getString("MCL_REMARK")), intVal(rs, "N_RID"), intVal(rs, "E_90702_RID"), dec(rs, "BETRAG"), ts(rs, "CR_DATE"), intVal(rs, "E_90700_RID"), intVal(rs, "E_90699_RID"), intVal(rs, "ANZAHL")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.PersonTaskAssignmentRow> fetchPersonTaskAssignments(String watermark) {
|
||||
return queryByWatermark("E_90709", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, REKEY01, REKEY02, REKEY03, RTYPE, MCL_LASTWORK, MCL_EFFORT_PLAN from E_90709", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.PersonTaskAssignmentRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("REKEY01")), trim(rs.getString("REKEY02")), trim(rs.getString("REKEY03")), trim(rs.getString("RTYPE")), ts(rs, "MCL_LASTWORK"), dec(rs, "MCL_EFFORT_PLAN")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.TimeRecordingRow> fetchTimeRecordings(String watermark) {
|
||||
return queryByWatermark("E_90716", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, REKEY01, LFDNR, RECORD_TYPE, MCL_ID, MCL_DESC, MCL_FROM, MCL_TO, MCL_EFFORT, MCL_REMARK, MCL_URL, RID1 from E_90716", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.TimeRecordingRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("REKEY01")), intVal(rs, "LFDNR"), trim(rs.getString("RECORD_TYPE")), trim(rs.getString("MCL_ID")), trim(rs.getString("MCL_DESC")), ts(rs, "MCL_FROM"), ts(rs, "MCL_TO"), dec(rs, "MCL_EFFORT"), trim(rs.getString("MCL_REMARK")), trim(rs.getString("MCL_URL")), intVal(rs, "RID1")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeitstandSourceRows.TimeRecordingAssignmentRow> fetchTimeRecordingAssignments(String watermark) {
|
||||
return queryByWatermark("E_90710", "select DBK, DBK_UID, UPD, UPD_ANZ, UPD_UID, STATUS, REKEY01, REKEY02, MCL_DESC, MCL_EFFORT, MCL_REMARK from E_90710", watermark,
|
||||
(rs, n) -> new LeitstandSourceRows.TimeRecordingAssignmentRow(trim(rs.getString("DBK")), trim(rs.getString("DBK_UID")), trim(rs.getString("UPD")), intVal(rs, "UPD_ANZ"), trim(rs.getString("UPD_UID")), intVal(rs, "STATUS"), trim(rs.getString("REKEY01")), trim(rs.getString("REKEY02")), trim(rs.getString("MCL_DESC")), dec(rs, "MCL_EFFORT"), trim(rs.getString("MCL_REMARK"))));
|
||||
}
|
||||
|
||||
private <T> List<T> queryByWatermark(String table, String baseSelect, String watermark, RowMapper<T> mapper) {
|
||||
StringBuilder sql = new StringBuilder(baseSelect);
|
||||
Map<String, ?> params = Map.of();
|
||||
if (watermark != null && !watermark.isBlank() && properties.getLeitstand().isIncrementalEnabled()) {
|
||||
sql.append(" where UPD > :watermark");
|
||||
params = Map.of("watermark", watermark);
|
||||
}
|
||||
sql.append(" order by ");
|
||||
if (!"E_90735".equals(table)) sql.append("UPD asc, ");
|
||||
sql.append("DBK asc");
|
||||
return jdbcTemplate.query(sql.toString(), params, mapper);
|
||||
}
|
||||
|
||||
private Integer intVal(ResultSet rs, String name) throws SQLException { Object v = rs.getObject(name); return v == null ? null : ((Number) v).intValue(); }
|
||||
private BigDecimal dec(ResultSet rs, String name) throws SQLException { Object v = rs.getObject(name); if (v == null) return null; return v instanceof BigDecimal bd ? bd : new BigDecimal(v.toString()); }
|
||||
private OffsetDateTime ts(ResultSet rs, String name) throws SQLException { Timestamp ts = rs.getTimestamp(name); return ts == null ? null : ts.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime(); }
|
||||
private String trim(String value) { return value == null ? null : value.trim(); }
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package at.procon.dip.domain.time.leitstand.source;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public final class LeitstandSourceRows {
|
||||
private LeitstandSourceRows() {}
|
||||
|
||||
public record ActivityTypeRow(Integer id, String lCode, String bez) {}
|
||||
public record PersonRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
Integer personNumber, String lastName, String firstName, String categoryDbk,
|
||||
String salutation, String title, String street, String postalCode, String city,
|
||||
String countryCode, String phone, String fax, String email, BigDecimal costPerHour,
|
||||
String organizationDbk) {}
|
||||
public record OrganizationRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
String betreu, String name, String street, String postalCode, String city,
|
||||
String phone, String fax, String email, Integer orgNumber, String shortName) {}
|
||||
public record ContractRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
String organizationDbk, Integer lfdnr, String name, String iref, String eref,
|
||||
String description, OffsetDateTime validFrom, OffsetDateTime validTo) {}
|
||||
public record ContractPositionRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
String contractDbk, Integer lfdnr, String projectName, BigDecimal salesPrice,
|
||||
BigDecimal purchasePrice, String description, OffsetDateTime validFrom,
|
||||
OffsetDateTime validTo, String name, String iref, String eref) {}
|
||||
public record CostUnitRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
String personDbk, String organizationDbk, String contractDbk, String legacyRelation4Dbk,
|
||||
String legacyRelation5Dbk, String contractPositionDbk, String projectName,
|
||||
Integer projectId, Integer projectTask, String mclId, String mclName, String mclDesc,
|
||||
OffsetDateTime validFrom, OffsetDateTime validTo, BigDecimal effortPlan) {}
|
||||
public record TaskRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
String primaryCostUnitDbk, String secondaryCostUnitDbk, String tertiaryCostUnitDbk,
|
||||
String parentTaskDbk, Integer lfdnr, String taskType, String mclId, String mclName,
|
||||
String mclDesc, OffsetDateTime validFrom, OffsetDateTime validTo, BigDecimal effortPlan,
|
||||
OffsetDateTime lastWork, Integer completionPercent, OffsetDateTime doneDate, String remark,
|
||||
Integer nRid, Integer legacy90702Rid, BigDecimal amount, OffsetDateTime createdSourceAt,
|
||||
Integer legacy90700Rid, Integer legacy90699Rid, Integer quantity) {}
|
||||
public record PersonTaskAssignmentRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid,
|
||||
Integer status, String taskDbk, String personDbk, String costUnitDbk,
|
||||
String rtype, OffsetDateTime lastWork, BigDecimal effortPlan) {}
|
||||
public record TimeRecordingRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid, Integer status,
|
||||
String personDbk, Integer lfdnr, String recordType, String mclId, String mclDesc,
|
||||
OffsetDateTime recordedFrom, OffsetDateTime recordedTo, BigDecimal effort,
|
||||
String remark, String url, Integer activityTypeId) {}
|
||||
public record TimeRecordingAssignmentRow(String dbk, String dbkUid, String upd, Integer updAnz, String updUid,
|
||||
Integer status, String personTaskAssignmentDbk, String timeRecordingDbk,
|
||||
String mclDesc, BigDecimal effort, String remark) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package at.procon.dip.domain.time.leitstand.source;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface LeitstandTimeSourceClient {
|
||||
List<LeitstandSourceRows.ActivityTypeRow> fetchActivityTypes();
|
||||
List<LeitstandSourceRows.PersonRow> fetchPersons(String watermark);
|
||||
List<LeitstandSourceRows.OrganizationRow> fetchOrganizations(String watermark);
|
||||
List<LeitstandSourceRows.ContractRow> fetchContracts(String watermark);
|
||||
List<LeitstandSourceRows.ContractPositionRow> fetchContractPositions(String watermark);
|
||||
List<LeitstandSourceRows.CostUnitRow> fetchCostUnits(String watermark);
|
||||
List<LeitstandSourceRows.TaskRow> fetchTasks(String watermark);
|
||||
List<LeitstandSourceRows.PersonTaskAssignmentRow> fetchPersonTaskAssignments(String watermark);
|
||||
List<LeitstandSourceRows.TimeRecordingRow> fetchTimeRecordings(String watermark);
|
||||
List<LeitstandSourceRows.TimeRecordingAssignmentRow> fetchTimeRecordingAssignments(String watermark);
|
||||
}
|
||||
|
|
@ -11,6 +11,6 @@ public interface TimeEntrySourceLinkRepository extends JpaRepository<TimeEntrySo
|
|||
|
||||
List<TimeEntrySourceLink> findByTimeEntry_Id(UUID timeEntryId);
|
||||
|
||||
Optional<TimeEntrySourceLink> findBySourceSystemAndSourceEntityTypeAndSourceExternalId(
|
||||
TimeSourceSystem sourceSystem, String sourceEntityType, String sourceExternalId);
|
||||
Optional<TimeEntrySourceLink> findByTimeEntry_IdAndSourceSystemAndSourceEntityTypeAndSourceExternalId(
|
||||
UUID timeEntryId, TimeSourceSystem sourceSystem, String sourceEntityType, String sourceExternalId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandActivityType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandActivityTypeRepository extends JpaRepository<LeitstandActivityType, Integer> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandContractPosition;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandContractPositionRepository extends JpaRepository<LeitstandContractPosition, String> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandContract;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandContractRepository extends JpaRepository<LeitstandContract, String> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandCostUnit;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandCostUnitRepository extends JpaRepository<LeitstandCostUnit, String> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandOrganization;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandOrganizationRepository extends JpaRepository<LeitstandOrganization, String> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandPerson;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandPersonRepository extends JpaRepository<LeitstandPerson, String> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandPersonTaskAssignment;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandPersonTaskAssignmentRepository extends JpaRepository<LeitstandPersonTaskAssignment, String> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandTask;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandTaskRepository extends JpaRepository<LeitstandTask, String> {}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandTimeRecordingAssignment;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandTimeRecordingAssignmentRepository extends JpaRepository<LeitstandTimeRecordingAssignment, String> {
|
||||
List<LeitstandTimeRecordingAssignment> findByTimeRecordingDbk(String timeRecordingDbk);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package at.procon.dip.domain.time.repository.leitstand;
|
||||
|
||||
import at.procon.dip.domain.time.entity.leitstand.LeitstandTimeRecording;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LeitstandTimeRecordingRepository extends JpaRepository<LeitstandTimeRecording, String> {
|
||||
Optional<LeitstandTimeRecording> findByTimeEntry_Id(UUID timeEntryId);
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
package at.procon.dip.domain.time.service;
|
||||
|
||||
import at.procon.dip.domain.access.DocumentVisibility;
|
||||
import at.procon.dip.domain.document.DocumentFamily;
|
||||
import at.procon.dip.domain.document.DocumentStatus;
|
||||
import at.procon.dip.domain.document.DocumentType;
|
||||
import at.procon.dip.domain.document.entity.Document;
|
||||
import at.procon.dip.domain.document.repository.DocumentRepository;
|
||||
import at.procon.dip.domain.time.config.TimeDomainProperties;
|
||||
import at.procon.dip.domain.time.entity.*;
|
||||
import at.procon.dip.domain.time.leitstand.source.LeitstandSourceRows;
|
||||
import at.procon.dip.domain.time.leitstand.source.LeitstandTimeSourceClient;
|
||||
import at.procon.dip.domain.time.repository.TimeEntryRepository;
|
||||
import at.procon.dip.domain.time.repository.TimeEntrySourceLinkRepository;
|
||||
import at.procon.dip.domain.time.repository.TimeSyncRunRepository;
|
||||
import at.procon.dip.domain.time.repository.TimeSyncStateRepository;
|
||||
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
|
||||
import at.procon.dip.runtime.config.RuntimeMode;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
|
||||
@ConditionalOnProperty(prefix = "dip.time.leitstand", name = "enabled", havingValue = "true")
|
||||
@Slf4j
|
||||
public class LeitstandTimeImportService {
|
||||
|
||||
private static final String GLOBAL_SCOPE = "leitstand:all";
|
||||
|
||||
private final TimeDomainProperties properties;
|
||||
private final LeitstandTimeSourceClient sourceClient;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final TimeSyncRunRepository syncRunRepository;
|
||||
private final TimeSyncStateRepository syncStateRepository;
|
||||
private final DocumentRepository documentRepository;
|
||||
private final TimeEntryRepository timeEntryRepository;
|
||||
private final TimeEntrySourceLinkRepository sourceLinkRepository;
|
||||
|
||||
public LeitstandTimeImportService(
|
||||
@Qualifier("applicationJdbcTemplate") JdbcTemplate targetJdbcTemplate,
|
||||
TimeDomainProperties properties,
|
||||
LeitstandTimeSourceClient sourceClient,
|
||||
TimeSyncRunRepository syncRunRepository,
|
||||
TimeSyncStateRepository syncStateRepository,
|
||||
DocumentRepository documentRepository,
|
||||
TimeEntryRepository timeEntryRepository,
|
||||
TimeEntrySourceLinkRepository sourceLinkRepository
|
||||
) {
|
||||
this. properties = properties;
|
||||
this.jdbcTemplate = targetJdbcTemplate;
|
||||
this.sourceClient = sourceClient;
|
||||
this.syncRunRepository = syncRunRepository;
|
||||
this.syncStateRepository = syncStateRepository;
|
||||
this.documentRepository = documentRepository;
|
||||
this.timeEntryRepository = timeEntryRepository;
|
||||
this.sourceLinkRepository = sourceLinkRepository;
|
||||
}
|
||||
|
||||
public void runSync() {
|
||||
TimeSyncRun run = syncRunRepository.save(TimeSyncRun.builder()
|
||||
.sourceSystem(TimeSourceSystem.LEITSTAND)
|
||||
.runType(syncStateRepository.findBySourceSystemAndScopeKey(TimeSourceSystem.LEITSTAND, GLOBAL_SCOPE).isPresent() ? TimeSyncRunType.INCREMENTAL : TimeSyncRunType.INITIAL)
|
||||
.scopeKey(properties.getLeitstand().getScopeKey())
|
||||
.status(TimeSyncRunStatus.RUNNING)
|
||||
.initiatedBy("startup-runner")
|
||||
.build());
|
||||
try {
|
||||
syncActivityTypes(run);
|
||||
syncPersons(run);
|
||||
syncOrganizations(run);
|
||||
syncContracts(run);
|
||||
syncContractPositions(run);
|
||||
syncCostUnits(run);
|
||||
syncTasks(run);
|
||||
syncPersonTaskAssignments(run);
|
||||
List<LeitstandSourceRows.TimeRecordingRow> recordings = syncTimeRecordings(run);
|
||||
syncTimeRecordingAssignments(run);
|
||||
if (properties.getLeitstand().isCreateCanonicalTimeEntries()) {
|
||||
upsertCanonicalTimeEntries(recordings);
|
||||
}
|
||||
run.setStatus(TimeSyncRunStatus.COMPLETED);
|
||||
run.setFinishedAt(OffsetDateTime.now());
|
||||
syncRunRepository.save(run);
|
||||
updateGlobalState(run, null);
|
||||
} catch (Exception ex) {
|
||||
run.setStatus(TimeSyncRunStatus.FAILED);
|
||||
run.setFinishedAt(OffsetDateTime.now());
|
||||
run.setErrorMessage(ex.getMessage());
|
||||
syncRunRepository.save(run);
|
||||
updateGlobalState(run, ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private void syncActivityTypes(TimeSyncRun run) {
|
||||
List<LeitstandSourceRows.ActivityTypeRow> rows = sourceClient.fetchActivityTypes();
|
||||
run.setRowsRead(run.getRowsRead() + rows.size());
|
||||
jdbcTemplate.batchUpdate("""
|
||||
INSERT INTO "time".ls_activity_type(id, l_code, bez)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET l_code = EXCLUDED.l_code, bez = EXCLUDED.bez
|
||||
""", new BatchPreparedStatementSetter() {
|
||||
public void setValues(PreparedStatement ps, int i) throws java.sql.SQLException {
|
||||
var r = rows.get(i);
|
||||
ps.setObject(1, r.id()); ps.setString(2, r.lCode()); ps.setString(3, r.bez());
|
||||
}
|
||||
public int getBatchSize() { return rows.size(); }
|
||||
});
|
||||
updateTableState("activity-type", null, rows.isEmpty() ? null : String.valueOf(rows.get(rows.size()-1).id()));
|
||||
}
|
||||
|
||||
private void syncPersons(TimeSyncRun run) {
|
||||
String watermark = watermark("person");
|
||||
List<LeitstandSourceRows.PersonRow> rows = sourceClient.fetchPersons(watermark);
|
||||
run.setRowsRead(run.getRowsRead() + rows.size());
|
||||
batchUpsert("""
|
||||
INSERT INTO "time".ls_person(dbk, dbk_uid, upd, upd_anz, upd_uid, status, pers_nr, last_name, first_name, category_dbk, salutation, title, street, postal_code, city, country_code, phone, fax, email, cost_per_hour, organization_dbk)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, pers_nr=EXCLUDED.pers_nr, last_name=EXCLUDED.last_name, first_name=EXCLUDED.first_name, category_dbk=EXCLUDED.category_dbk, salutation=EXCLUDED.salutation, title=EXCLUDED.title, street=EXCLUDED.street, postal_code=EXCLUDED.postal_code, city=EXCLUDED.city, country_code=EXCLUDED.country_code, phone=EXCLUDED.phone, fax=EXCLUDED.fax, email=EXCLUDED.email, cost_per_hour=EXCLUDED.cost_per_hour, organization_dbk=EXCLUDED.organization_dbk, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps, r) -> { set(ps,1,r.dbk()); set(ps,2,r.dbkUid()); set(ps,3,r.upd()); set(ps,4,r.updAnz()); set(ps,5,r.updUid()); set(ps,6,r.status()); set(ps,7,r.personNumber()); set(ps,8,r.lastName()); set(ps,9,r.firstName()); set(ps,10,r.categoryDbk()); set(ps,11,r.salutation()); set(ps,12,r.title()); set(ps,13,r.street()); set(ps,14,r.postalCode()); set(ps,15,r.city()); set(ps,16,r.countryCode()); set(ps,17,r.phone()); set(ps,18,r.fax()); set(ps,19,r.email()); set(ps,20,r.costPerHour()); set(ps,21,r.organizationDbk());});
|
||||
updateTableState("person", maxUpd(rows, LeitstandSourceRows.PersonRow::upd), lastId(rows, LeitstandSourceRows.PersonRow::dbk));
|
||||
}
|
||||
|
||||
private void syncOrganizations(TimeSyncRun run) { var rows = sourceClient.fetchOrganizations(watermark("organization")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_organization(dbk, dbk_uid, upd, upd_anz, upd_uid, status, betreu, name, street, postal_code, city, phone, fax, email, org_nr, short_name) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, betreu=EXCLUDED.betreu, name=EXCLUDED.name, street=EXCLUDED.street, postal_code=EXCLUDED.postal_code, city=EXCLUDED.city, phone=EXCLUDED.phone, fax=EXCLUDED.fax, email=EXCLUDED.email, org_nr=EXCLUDED.org_nr, short_name=EXCLUDED.short_name, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.betreu());set(ps,8,r.name());set(ps,9,r.street());set(ps,10,r.postalCode());set(ps,11,r.city());set(ps,12,r.phone());set(ps,13,r.fax());set(ps,14,r.email());set(ps,15,r.orgNumber());set(ps,16,r.shortName());}); updateTableState("organization", maxUpd(rows, LeitstandSourceRows.OrganizationRow::upd), lastId(rows, LeitstandSourceRows.OrganizationRow::dbk)); }
|
||||
private void syncContracts(TimeSyncRun run) { var rows = sourceClient.fetchContracts(watermark("contract")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_contract(dbk, dbk_uid, upd, upd_anz, upd_uid, status, organization_dbk, lfdnr, name, iref, eref, description, valid_from, valid_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, organization_dbk=EXCLUDED.organization_dbk, lfdnr=EXCLUDED.lfdnr, name=EXCLUDED.name, iref=EXCLUDED.iref, eref=EXCLUDED.eref, description=EXCLUDED.description, valid_from=EXCLUDED.valid_from, valid_to=EXCLUDED.valid_to, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.organizationDbk());set(ps,8,r.lfdnr());set(ps,9,r.name());set(ps,10,r.iref());set(ps,11,r.eref());set(ps,12,r.description());set(ps,13,ts(r.validFrom()));set(ps,14,ts(r.validTo()));}); updateTableState("contract", maxUpd(rows, LeitstandSourceRows.ContractRow::upd), lastId(rows, LeitstandSourceRows.ContractRow::dbk)); }
|
||||
private void syncContractPositions(TimeSyncRun run) { var rows = sourceClient.fetchContractPositions(watermark("contract-position")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_contract_position(dbk, dbk_uid, upd, upd_anz, upd_uid, status, contract_dbk, lfdnr, project_name, sales_price, purchase_price, description, valid_from, valid_to, name, iref, eref) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, contract_dbk=EXCLUDED.contract_dbk, lfdnr=EXCLUDED.lfdnr, project_name=EXCLUDED.project_name, sales_price=EXCLUDED.sales_price, purchase_price=EXCLUDED.purchase_price, description=EXCLUDED.description, valid_from=EXCLUDED.valid_from, valid_to=EXCLUDED.valid_to, name=EXCLUDED.name, iref=EXCLUDED.iref, eref=EXCLUDED.eref, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.contractDbk());set(ps,8,r.lfdnr());set(ps,9,r.projectName());set(ps,10,r.salesPrice());set(ps,11,r.purchasePrice());set(ps,12,r.description());set(ps,13,ts(r.validFrom()));set(ps,14,ts(r.validTo()));set(ps,15,r.name());set(ps,16,r.iref());set(ps,17,r.eref());}); updateTableState("contract-position", maxUpd(rows, LeitstandSourceRows.ContractPositionRow::upd), lastId(rows, LeitstandSourceRows.ContractPositionRow::dbk)); }
|
||||
private void syncCostUnits(TimeSyncRun run) { var rows = sourceClient.fetchCostUnits(watermark("cost-unit")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_cost_unit(dbk, dbk_uid, upd, upd_anz, upd_uid, status, person_dbk, organization_dbk, contract_dbk, legacy_relation_4_dbk, legacy_relation_5_dbk, contract_position_dbk, project_name, project_id, project_task, mcl_id, mcl_name, mcl_desc, valid_from, valid_to, effort_plan) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, person_dbk=EXCLUDED.person_dbk, organization_dbk=EXCLUDED.organization_dbk, contract_dbk=EXCLUDED.contract_dbk, legacy_relation_4_dbk=EXCLUDED.legacy_relation_4_dbk, legacy_relation_5_dbk=EXCLUDED.legacy_relation_5_dbk, contract_position_dbk=EXCLUDED.contract_position_dbk, project_name=EXCLUDED.project_name, project_id=EXCLUDED.project_id, project_task=EXCLUDED.project_task, mcl_id=EXCLUDED.mcl_id, mcl_name=EXCLUDED.mcl_name, mcl_desc=EXCLUDED.mcl_desc, valid_from=EXCLUDED.valid_from, valid_to=EXCLUDED.valid_to, effort_plan=EXCLUDED.effort_plan, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.personDbk());set(ps,8,r.organizationDbk());set(ps,9,r.contractDbk());set(ps,10,r.legacyRelation4Dbk());set(ps,11,r.legacyRelation5Dbk());set(ps,12,r.contractPositionDbk());set(ps,13,r.projectName());set(ps,14,r.projectId());set(ps,15,r.projectTask());set(ps,16,r.mclId());set(ps,17,r.mclName());set(ps,18,r.mclDesc());set(ps,19,ts(r.validFrom()));set(ps,20,ts(r.validTo()));set(ps,21,r.effortPlan());}); updateTableState("cost-unit", maxUpd(rows, LeitstandSourceRows.CostUnitRow::upd), lastId(rows, LeitstandSourceRows.CostUnitRow::dbk)); }
|
||||
private void syncTasks(TimeSyncRun run) { var rows = sourceClient.fetchTasks(watermark("task")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_task(dbk, dbk_uid, upd, upd_anz, upd_uid, status, primary_cost_unit_dbk, secondary_cost_unit_dbk, tertiary_cost_unit_dbk, parent_task_dbk, lfdnr, task_type, mcl_id, mcl_name, mcl_desc, valid_from, valid_to, effort_plan, last_work, completion_percent, done_date, remark, n_rid, legacy_90702_rid, amount, created_source_at, legacy_90700_rid, legacy_90699_rid, quantity) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, primary_cost_unit_dbk=EXCLUDED.primary_cost_unit_dbk, secondary_cost_unit_dbk=EXCLUDED.secondary_cost_unit_dbk, tertiary_cost_unit_dbk=EXCLUDED.tertiary_cost_unit_dbk, parent_task_dbk=EXCLUDED.parent_task_dbk, lfdnr=EXCLUDED.lfdnr, task_type=EXCLUDED.task_type, mcl_id=EXCLUDED.mcl_id, mcl_name=EXCLUDED.mcl_name, mcl_desc=EXCLUDED.mcl_desc, valid_from=EXCLUDED.valid_from, valid_to=EXCLUDED.valid_to, effort_plan=EXCLUDED.effort_plan, last_work=EXCLUDED.last_work, completion_percent=EXCLUDED.completion_percent, done_date=EXCLUDED.done_date, remark=EXCLUDED.remark, n_rid=EXCLUDED.n_rid, legacy_90702_rid=EXCLUDED.legacy_90702_rid, amount=EXCLUDED.amount, created_source_at=EXCLUDED.created_source_at, legacy_90700_rid=EXCLUDED.legacy_90700_rid, legacy_90699_rid=EXCLUDED.legacy_90699_rid, quantity=EXCLUDED.quantity, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.primaryCostUnitDbk());set(ps,8,r.secondaryCostUnitDbk());set(ps,9,r.tertiaryCostUnitDbk());set(ps,10,r.parentTaskDbk());set(ps,11,r.lfdnr());set(ps,12,r.taskType());set(ps,13,r.mclId());set(ps,14,r.mclName());set(ps,15,r.mclDesc());set(ps,16,ts(r.validFrom()));set(ps,17,ts(r.validTo()));set(ps,18,r.effortPlan());set(ps,19,ts(r.lastWork()));set(ps,20,r.completionPercent());set(ps,21,ts(r.doneDate()));set(ps,22,r.remark());set(ps,23,r.nRid());set(ps,24,r.legacy90702Rid());set(ps,25,r.amount());set(ps,26,ts(r.createdSourceAt()));set(ps,27,r.legacy90700Rid());set(ps,28,r.legacy90699Rid());set(ps,29,r.quantity());}); updateTableState("task", maxUpd(rows, LeitstandSourceRows.TaskRow::upd), lastId(rows, LeitstandSourceRows.TaskRow::dbk)); }
|
||||
private void syncPersonTaskAssignments(TimeSyncRun run) { var rows = sourceClient.fetchPersonTaskAssignments(watermark("person-task-assignment")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_person_task_assignment(dbk, dbk_uid, upd, upd_anz, upd_uid, status, task_dbk, person_dbk, cost_unit_dbk, rtype, last_work, effort_plan) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, task_dbk=EXCLUDED.task_dbk, person_dbk=EXCLUDED.person_dbk, cost_unit_dbk=EXCLUDED.cost_unit_dbk, rtype=EXCLUDED.rtype, last_work=EXCLUDED.last_work, effort_plan=EXCLUDED.effort_plan, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.taskDbk());set(ps,8,r.personDbk());set(ps,9,r.costUnitDbk());set(ps,10,r.rtype());set(ps,11,ts(r.lastWork()));set(ps,12,r.effortPlan());}); updateTableState("person-task-assignment", maxUpd(rows, LeitstandSourceRows.PersonTaskAssignmentRow::upd), lastId(rows, LeitstandSourceRows.PersonTaskAssignmentRow::dbk)); }
|
||||
private List<LeitstandSourceRows.TimeRecordingRow> syncTimeRecordings(TimeSyncRun run) { var rows = sourceClient.fetchTimeRecordings(watermark("time-recording")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_time_recording(dbk, dbk_uid, upd, upd_anz, upd_uid, status, person_dbk, lfdnr, record_type, mcl_id, mcl_desc, recorded_from, recorded_to, effort, remark, url, activity_type_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, person_dbk=EXCLUDED.person_dbk, lfdnr=EXCLUDED.lfdnr, record_type=EXCLUDED.record_type, mcl_id=EXCLUDED.mcl_id, mcl_desc=EXCLUDED.mcl_desc, recorded_from=EXCLUDED.recorded_from, recorded_to=EXCLUDED.recorded_to, effort=EXCLUDED.effort, remark=EXCLUDED.remark, url=EXCLUDED.url, activity_type_id=EXCLUDED.activity_type_id, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.personDbk());set(ps,8,r.lfdnr());set(ps,9,r.recordType());set(ps,10,r.mclId());set(ps,11,r.mclDesc());set(ps,12,ts(r.recordedFrom()));set(ps,13,ts(r.recordedTo()));set(ps,14,r.effort());set(ps,15,r.remark());set(ps,16,r.url());set(ps,17,r.activityTypeId());}); updateTableState("time-recording", maxUpd(rows, LeitstandSourceRows.TimeRecordingRow::upd), lastId(rows, LeitstandSourceRows.TimeRecordingRow::dbk)); return rows; }
|
||||
private void syncTimeRecordingAssignments(TimeSyncRun run) { var rows = sourceClient.fetchTimeRecordingAssignments(watermark("time-recording-assignment")); run.setRowsRead(run.getRowsRead()+rows.size()); batchUpsert("""
|
||||
INSERT INTO "time".ls_time_recording_assignment(dbk, dbk_uid, upd, upd_anz, upd_uid, status, person_task_assignment_dbk, time_recording_dbk, mcl_desc, effort, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (dbk) DO UPDATE SET dbk_uid=EXCLUDED.dbk_uid, upd=EXCLUDED.upd, upd_anz=EXCLUDED.upd_anz, upd_uid=EXCLUDED.upd_uid, status=EXCLUDED.status, person_task_assignment_dbk=EXCLUDED.person_task_assignment_dbk, time_recording_dbk=EXCLUDED.time_recording_dbk, mcl_desc=EXCLUDED.mcl_desc, effort=EXCLUDED.effort, remark=EXCLUDED.remark, updated_at=CURRENT_TIMESTAMP
|
||||
""", rows, (ps,r)->{set(ps,1,r.dbk());set(ps,2,r.dbkUid());set(ps,3,r.upd());set(ps,4,r.updAnz());set(ps,5,r.updUid());set(ps,6,r.status());set(ps,7,r.personTaskAssignmentDbk());set(ps,8,r.timeRecordingDbk());set(ps,9,r.mclDesc());set(ps,10,r.effort());set(ps,11,r.remark());}); updateTableState("time-recording-assignment", maxUpd(rows, LeitstandSourceRows.TimeRecordingAssignmentRow::upd), lastId(rows, LeitstandSourceRows.TimeRecordingAssignmentRow::dbk)); }
|
||||
|
||||
@Transactional
|
||||
protected void upsertCanonicalTimeEntries(List<LeitstandSourceRows.TimeRecordingRow> rows) {
|
||||
Map<String, String> personNames = loadPersonNames();
|
||||
for (var row : rows) {
|
||||
String businessKey = "TIME:LEITSTAND:" + row.dbk();
|
||||
Document document = documentRepository.findByBusinessKey(businessKey).orElseGet(() -> Document.builder().businessKey(businessKey).documentType(DocumentType.TIME_ENTRY).documentFamily(DocumentFamily.TIME).visibility(DocumentVisibility.PUBLIC).status(DocumentStatus.RECEIVED).build());
|
||||
document.setTitle(buildTitle(row, personNames.get(row.personDbk())));
|
||||
document.setSummary(buildSummary(row));
|
||||
document = documentRepository.save(document);
|
||||
|
||||
Document finalDocument = document;
|
||||
TimeEntry entry = timeEntryRepository.findBySourceSystemAndExternalId(TimeSourceSystem.LEITSTAND, row.dbk()).orElseGet(() -> TimeEntry.builder().sourceSystem(TimeSourceSystem.LEITSTAND).externalId(row.dbk()).document(finalDocument).build());
|
||||
entry.setDocument(document);
|
||||
entry.setPersonExternalId(row.personDbk());
|
||||
entry.setPersonDisplayName(personNames.get(row.personDbk()));
|
||||
entry.setEntryStart(row.recordedFrom());
|
||||
entry.setEntryEnd(row.recordedTo());
|
||||
entry.setDurationSeconds(toSeconds(row.effort()));
|
||||
entry.setDescriptionShort(row.mclDesc());
|
||||
entry.setDescriptionLong(row.remark());
|
||||
entry.setRawStatus(row.status() == null ? null : String.valueOf(row.status()));
|
||||
entry.setSearchAnchorLabel(document.getTitle());
|
||||
entry = timeEntryRepository.save(entry);
|
||||
|
||||
jdbcTemplate.update("""
|
||||
update "time".ls_time_recording set time_entry_id = ? where dbk = ?
|
||||
""", entry.getId(), row.dbk());
|
||||
upsertSourceLink(entry, "TIME_RECORDING", row.dbk(), null);
|
||||
if (row.personDbk() != null) upsertSourceLink(entry, "PERSON", row.personDbk(), row.dbk());
|
||||
if (row.activityTypeId() != null) upsertSourceLink(entry, "ACTIVITY_TYPE", String.valueOf(row.activityTypeId()), row.dbk());
|
||||
}
|
||||
}
|
||||
|
||||
private void upsertSourceLink(TimeEntry entry, String type, String externalId, String parentExternalId) {
|
||||
TimeEntrySourceLink link = sourceLinkRepository.findByTimeEntry_Id(entry.getId()).stream()
|
||||
.filter(it -> it.getSourceSystem() == TimeSourceSystem.LEITSTAND && type.equals(it.getSourceEntityType()) && externalId.equals(it.getSourceExternalId()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> TimeEntrySourceLink.builder().timeEntry(entry).sourceSystem(TimeSourceSystem.LEITSTAND).sourceEntityType(type).sourceExternalId(externalId).build());
|
||||
link.setParentSourceExternalId(parentExternalId);
|
||||
sourceLinkRepository.save(link);
|
||||
}
|
||||
|
||||
private Map<String, String> loadPersonNames() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
jdbcTemplate.query("""
|
||||
select dbk, first_name, last_name from "time".ls_person
|
||||
""", rs -> {
|
||||
result.put(rs.getString("dbk"), firstNonBlank(joinName(rs.getString("first_name"), rs.getString("last_name")), rs.getString("last_name"), rs.getString("dbk")));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private String buildTitle(LeitstandSourceRows.TimeRecordingRow row, String personName) {
|
||||
String base = firstNonBlank(row.mclDesc(), row.remark(), row.mclId());
|
||||
if (base == null) base = "Time entry " + row.dbk();
|
||||
return personName == null ? base : personName + " - " + base;
|
||||
}
|
||||
|
||||
private String buildSummary(LeitstandSourceRows.TimeRecordingRow row) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (row.mclDesc() != null && !row.mclDesc().isBlank()) sb.append(row.mclDesc().trim());
|
||||
if (row.remark() != null && !row.remark().isBlank()) { if (sb.length() > 0) sb.append(" | "); sb.append(row.remark().trim()); }
|
||||
if (row.mclId() != null && !row.mclId().isBlank()) { if (sb.length() > 0) sb.append(" | "); sb.append("ID ").append(row.mclId().trim()); }
|
||||
return sb.length() == 0 ? null : sb.toString();
|
||||
}
|
||||
|
||||
private Long toSeconds(BigDecimal effort) { return effort == null ? null : effort.multiply(BigDecimal.valueOf(3600)).setScale(0, RoundingMode.HALF_UP).longValue(); }
|
||||
private String joinName(String first, String last) { return ((first == null ? "" : first.trim()) + (last == null ? "" : " " + last.trim())).trim(); }
|
||||
private String firstNonBlank(String... vals) { for (String v : vals) if (v != null && !v.trim().isEmpty()) return v.trim(); return null; }
|
||||
private String watermark(String scope) { return properties.getLeitstand().isIncrementalEnabled() ? syncStateRepository.findBySourceSystemAndScopeKey(TimeSourceSystem.LEITSTAND, tableScope(scope)).map(TimeSyncState::getLastSeenWatermark).orElse(null) : null; }
|
||||
private String tableScope(String scope) { return "leitstand:" + scope; }
|
||||
private <T> String lastId(List<T> rows, java.util.function.Function<T,String> id) { return rows.isEmpty() ? null : id.apply(rows.get(rows.size()-1)); }
|
||||
private <T> String maxUpd(List<T> rows, java.util.function.Function<T,String> upd) { return rows.stream().map(upd).filter(v -> v != null && !v.isBlank()).max(String::compareTo).orElse(null); }
|
||||
|
||||
@Transactional
|
||||
protected void updateTableState(String scope, String watermark, String lastExternalId) {
|
||||
TimeSyncState state = syncStateRepository.findBySourceSystemAndScopeKey(TimeSourceSystem.LEITSTAND, tableScope(scope)).orElseGet(() -> TimeSyncState.builder().sourceSystem(TimeSourceSystem.LEITSTAND).scopeKey(tableScope(scope)).build());
|
||||
state.setLastAttemptedSyncAt(OffsetDateTime.now());
|
||||
state.setLastSuccessfulSyncAt(OffsetDateTime.now());
|
||||
state.setLastSeenWatermark(watermark);
|
||||
state.setLastSeenExternalId(lastExternalId);
|
||||
syncStateRepository.save(state);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
protected void updateGlobalState(TimeSyncRun run, String notes) {
|
||||
TimeSyncState state = syncStateRepository.findBySourceSystemAndScopeKey(TimeSourceSystem.LEITSTAND, GLOBAL_SCOPE).orElseGet(() -> TimeSyncState.builder().sourceSystem(TimeSourceSystem.LEITSTAND).scopeKey(GLOBAL_SCOPE).build());
|
||||
state.setLastAttemptedSyncAt(run.getStartedAt());
|
||||
if (run.getStatus() == TimeSyncRunStatus.COMPLETED) state.setLastSuccessfulSyncAt(run.getFinishedAt());
|
||||
state.setLastRun(run);
|
||||
state.setNotes(notes);
|
||||
syncStateRepository.save(state);
|
||||
}
|
||||
|
||||
private <T> void batchUpsert(String sql, List<T> rows, Binder<T> binder) {
|
||||
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
|
||||
@Override public void setValues(PreparedStatement ps, int i) throws java.sql.SQLException { binder.bind(ps, rows.get(i)); }
|
||||
@Override public int getBatchSize() { return rows.size(); }
|
||||
});
|
||||
}
|
||||
|
||||
private void set(PreparedStatement ps, int index, Object value) throws java.sql.SQLException { ps.setObject(index, value); }
|
||||
private Timestamp ts(OffsetDateTime t) { return t == null ? null : Timestamp.from(t.toInstant()); }
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Binder<T> { void bind(PreparedStatement ps, T row) throws java.sql.SQLException; }
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package at.procon.dip.domain.time.startup;
|
||||
|
||||
import at.procon.dip.domain.time.service.LeitstandTimeImportService;
|
||||
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
|
||||
import at.procon.dip.runtime.config.RuntimeMode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
|
||||
@ConditionalOnProperty(prefix = "dip.time.leitstand", name = {"enabled", "startup-sync-enabled"}, havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LeitstandTimeImportStartupRunner implements ApplicationRunner {
|
||||
private final LeitstandTimeImportService importService;
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
log.info("Starting Leitstand TIME import (T2)");
|
||||
importService.runSync();
|
||||
}
|
||||
}
|
||||
|
|
@ -293,13 +293,19 @@ dip:
|
|||
# Delete tar.gz after ingestion
|
||||
delete-after-ingestion: true
|
||||
|
||||
|
||||
time:
|
||||
enabled: false
|
||||
enabled: true
|
||||
leitstand:
|
||||
enabled: false
|
||||
enabled: true
|
||||
startup-sync-enabled: true
|
||||
import-batch-id: time-leitstand
|
||||
reconcile-lookback-days: 7
|
||||
create-canonical-time-entries: true
|
||||
jdbc:
|
||||
url: jdbc:jtds:sqlserver://mag2:1433;databaseName=spc
|
||||
username: sa
|
||||
password: jhcbxr
|
||||
driver-class-name: net.sourceforge.jtds.jdbc.Driver
|
||||
toggl-track:
|
||||
enabled: false
|
||||
import-batch-id: time-toggl
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
-- Attribute catalog for text-import name/value pairs.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS DOC.doc_attribute_name (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
attribute_name VARCHAR(255) NOT NULL,
|
||||
normalized_name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS DOC.doc_document_attribute (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
document_id UUID NOT NULL REFERENCES DOC.doc_document(id) ON DELETE CASCADE,
|
||||
attribute_name_id UUID NOT NULL REFERENCES DOC.doc_attribute_name(id),
|
||||
attribute_value TEXT NOT NULL,
|
||||
attribute_value_hash VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_doc_attr_name_normalized ON DOC.doc_attribute_name(normalized_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_doc_attr_name_name ON DOC.doc_attribute_name(attribute_name);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_doc_doc_attr_document ON DOC.doc_document_attribute(document_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_doc_doc_attr_name ON DOC.doc_document_attribute(attribute_name_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_doc_doc_attr_value_hash ON DOC.doc_document_attribute(attribute_value_hash);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_doc_doc_attr_doc_name_hash
|
||||
ON DOC.doc_document_attribute(document_id, attribute_name_id, attribute_value_hash);
|
||||
|
||||
COMMENT ON TABLE DOC.doc_attribute_name IS 'Global catalog of reusable metadata attribute names';
|
||||
COMMENT ON TABLE DOC.doc_document_attribute IS 'Intersection table linking documents to catalog-backed name/value attributes';
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
-- TIME Phase T1 foundation for shared time-entry structures in the NEW runtime.
|
||||
-- TIME Phase T1 foundation for shared time-entry structures in the NEW run"time".
|
||||
-- No source import logic yet; this migration only creates the shared TIME schema and base state tables.
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS TIME;
|
||||
|
|
@ -13,7 +13,7 @@ BEGIN
|
|||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'time_source_system' AND n.nspname = 'time'
|
||||
) THEN
|
||||
CREATE TYPE TIME.time_source_system AS ENUM ('LEITSTAND', 'TOGGL_TRACK');
|
||||
CREATE TYPE "time".time_source_system AS ENUM ('LEITSTAND', 'TOGGL_TRACK');
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -26,7 +26,7 @@ BEGIN
|
|||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'time_sync_run_status' AND n.nspname = 'time'
|
||||
) THEN
|
||||
CREATE TYPE TIME.time_sync_run_status AS ENUM ('RUNNING', 'COMPLETED', 'FAILED');
|
||||
CREATE TYPE "time".time_sync_run_status AS ENUM ('RUNNING', 'COMPLETED', 'FAILED');
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -39,7 +39,7 @@ BEGIN
|
|||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'time_sync_run_type' AND n.nspname = 'time'
|
||||
) THEN
|
||||
CREATE TYPE TIME.time_sync_run_type AS ENUM ('INITIAL', 'INCREMENTAL', 'RECONCILE', 'MANUAL');
|
||||
CREATE TYPE "time".time_sync_run_type AS ENUM ('INITIAL', 'INCREMENTAL', 'RECONCILE', 'MANUAL');
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -53,7 +53,7 @@ BEGIN
|
|||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE n.nspname = 'doc' AND t.typname = 'doc_document_type' AND e.enumlabel = 'TIME_ENTRY'
|
||||
) THEN
|
||||
ALTER TYPE DOC.doc_document_type ADD VALUE 'TIME_ENTRY';
|
||||
ALTER TYPE doc.doc_document_type ADD VALUE 'TIME_ENTRY';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -67,15 +67,15 @@ BEGIN
|
|||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE n.nspname = 'doc' AND t.typname = 'doc_document_family' AND e.enumlabel = 'TIME'
|
||||
) THEN
|
||||
ALTER TYPE DOC.doc_document_family ADD VALUE 'TIME';
|
||||
ALTER TYPE doc.doc_document_family ADD VALUE 'TIME';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.time_entry (
|
||||
CREATE TABLE IF NOT EXISTS "time".time_entry (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
document_id UUID NOT NULL UNIQUE REFERENCES DOC.doc_document(id) ON DELETE CASCADE,
|
||||
source_system TIME.time_source_system NOT NULL,
|
||||
document_id UUID NOT NULL UNIQUE REFERENCES doc.doc_document(id) ON DELETE CASCADE,
|
||||
source_system "time".time_source_system NOT NULL,
|
||||
external_id VARCHAR(255) NOT NULL,
|
||||
person_external_id VARCHAR(255),
|
||||
person_display_name VARCHAR(255),
|
||||
|
|
@ -95,10 +95,10 @@ CREATE TABLE IF NOT EXISTS TIME.time_entry (
|
|||
CONSTRAINT uq_time_entry_source UNIQUE (source_system, external_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.time_entry_source_link (
|
||||
CREATE TABLE IF NOT EXISTS "time".time_entry_source_link (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
time_entry_id UUID NOT NULL REFERENCES TIME.time_entry(id) ON DELETE CASCADE,
|
||||
source_system TIME.time_source_system NOT NULL,
|
||||
time_entry_id UUID NOT NULL REFERENCES "time".time_entry(id) ON DELETE CASCADE,
|
||||
source_system "time".time_source_system NOT NULL,
|
||||
source_entity_type VARCHAR(120) NOT NULL,
|
||||
source_external_id VARCHAR(255) NOT NULL,
|
||||
linked_role VARCHAR(120),
|
||||
|
|
@ -110,12 +110,12 @@ CREATE TABLE IF NOT EXISTS TIME.time_entry_source_link (
|
|||
CONSTRAINT uq_time_entry_source_link UNIQUE (time_entry_id, source_system, source_entity_type, source_external_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.time_sync_run (
|
||||
CREATE TABLE IF NOT EXISTS "time".time_sync_run (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
source_system TIME.time_source_system NOT NULL,
|
||||
run_type TIME.time_sync_run_type NOT NULL,
|
||||
source_system "time".time_source_system NOT NULL,
|
||||
run_type "time".time_sync_run_type NOT NULL,
|
||||
scope_key VARCHAR(255) NOT NULL,
|
||||
status TIME.time_sync_run_status NOT NULL,
|
||||
status "time".time_sync_run_status NOT NULL,
|
||||
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
finished_at TIMESTAMP WITH TIME ZONE,
|
||||
watermark_from VARCHAR(255),
|
||||
|
|
@ -130,36 +130,36 @@ CREATE TABLE IF NOT EXISTS TIME.time_sync_run (
|
|||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.time_sync_state (
|
||||
CREATE TABLE IF NOT EXISTS "time".time_sync_state (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
source_system TIME.time_source_system NOT NULL,
|
||||
source_system "time".time_source_system NOT NULL,
|
||||
scope_key VARCHAR(255) NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
last_successful_sync_at TIMESTAMP WITH TIME ZONE,
|
||||
last_attempted_sync_at TIMESTAMP WITH TIME ZONE,
|
||||
last_seen_watermark VARCHAR(255),
|
||||
last_seen_external_id VARCHAR(255),
|
||||
last_run_id UUID REFERENCES TIME.time_sync_run(id) ON DELETE SET NULL,
|
||||
last_run_id UUID REFERENCES "time".time_sync_run(id) ON DELETE SET NULL,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_time_sync_state_scope UNIQUE (source_system, scope_key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_source_system ON TIME.time_entry(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_external_id ON TIME.time_entry(external_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_start ON TIME.time_entry(entry_start DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_end ON TIME.time_entry(entry_end DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_source_updated ON TIME.time_entry(source_updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_source_system ON "time".time_entry(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_external_id ON "time".time_entry(external_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_start ON "time".time_entry(entry_start DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_end ON "time".time_entry(entry_end DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_source_updated ON "time".time_entry(source_updated_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_link_entry ON TIME.time_entry_source_link(time_entry_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_link_source_lookup ON TIME.time_entry_source_link(source_system, source_entity_type, source_external_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_link_role ON TIME.time_entry_source_link(linked_role);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_link_entry ON "time".time_entry_source_link(time_entry_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_link_source_lookup ON "time".time_entry_source_link(source_system, source_entity_type, source_external_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_entry_link_role ON "time".time_entry_source_link(linked_role);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_source_system ON TIME.time_sync_run(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_scope_key ON TIME.time_sync_run(scope_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_status ON TIME.time_sync_run(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_started_at ON TIME.time_sync_run(started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_source_system ON "time".time_sync_run(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_scope_key ON "time".time_sync_run(scope_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_status ON "time".time_sync_run(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_run_started_at ON "time".time_sync_run(started_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_state_source_system ON TIME.time_sync_state(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_state_scope_key ON TIME.time_sync_state(scope_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_state_source_system ON "time".time_sync_state(source_system);
|
||||
CREATE INDEX IF NOT EXISTS idx_time_sync_state_scope_key ON "time".time_sync_state(scope_key);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
-- TIME Phase T2: Leitstand source import tables and canonical time-entry import scaffolding.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_activity_type (
|
||||
id INTEGER PRIMARY KEY,
|
||||
l_code VARCHAR(32),
|
||||
bez VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_person (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
pers_nr INTEGER, last_name VARCHAR(35), first_name VARCHAR(25), category_dbk VARCHAR(24),
|
||||
salutation VARCHAR(15), title VARCHAR(30), street VARCHAR(36), postal_code VARCHAR(6), city VARCHAR(30),
|
||||
country_code VARCHAR(4), phone VARCHAR(30), fax VARCHAR(30), email VARCHAR(50), cost_per_hour NUMERIC(8,3), organization_dbk VARCHAR(24),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_person_upd ON TIME.ls_person(upd);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_organization (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
betreu VARCHAR(2), name VARCHAR(255), street VARCHAR(50), postal_code VARCHAR(6), city VARCHAR(50), phone VARCHAR(30), fax VARCHAR(30), email VARCHAR(50), org_nr INTEGER, short_name VARCHAR(30),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_organization_upd ON TIME.ls_organization(upd);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_contract (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
organization_dbk VARCHAR(24), lfdnr INTEGER, name VARCHAR(255), iref VARCHAR(255), eref VARCHAR(255), description VARCHAR(255), valid_from TIMESTAMP WITH TIME ZONE, valid_to TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_contract_upd ON TIME.ls_contract(upd);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_contract_org_dbk ON TIME.ls_contract(organization_dbk);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_contract_position (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
contract_dbk VARCHAR(24), lfdnr INTEGER, project_name VARCHAR(255), sales_price NUMERIC(18,4), purchase_price NUMERIC(18,4), description VARCHAR(255), valid_from TIMESTAMP WITH TIME ZONE, valid_to TIMESTAMP WITH TIME ZONE,
|
||||
name VARCHAR(255), iref VARCHAR(255), eref VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_contract_position_upd ON TIME.ls_contract_position(upd);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_contract_position_contract_dbk ON TIME.ls_contract_position(contract_dbk);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_cost_unit (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
person_dbk VARCHAR(24), organization_dbk VARCHAR(24), contract_dbk VARCHAR(24), legacy_relation_4_dbk VARCHAR(24), legacy_relation_5_dbk VARCHAR(24), contract_position_dbk VARCHAR(24),
|
||||
project_name VARCHAR(255), project_id INTEGER, project_task INTEGER, mcl_id VARCHAR(255), mcl_name VARCHAR(255), mcl_desc VARCHAR(255), valid_from TIMESTAMP WITH TIME ZONE, valid_to TIMESTAMP WITH TIME ZONE, effort_plan NUMERIC(10,3),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_cost_unit_upd ON TIME.ls_cost_unit(upd);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_cost_unit_contract_dbk ON TIME.ls_cost_unit(contract_dbk);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_task (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
primary_cost_unit_dbk VARCHAR(24), secondary_cost_unit_dbk VARCHAR(24), tertiary_cost_unit_dbk VARCHAR(24), parent_task_dbk VARCHAR(24),
|
||||
lfdnr INTEGER, task_type VARCHAR(1), mcl_id VARCHAR(255), mcl_name VARCHAR(255), mcl_desc VARCHAR(8000), valid_from TIMESTAMP WITH TIME ZONE, valid_to TIMESTAMP WITH TIME ZONE, effort_plan NUMERIC(10,1),
|
||||
last_work TIMESTAMP WITH TIME ZONE, completion_percent INTEGER, done_date TIMESTAMP WITH TIME ZONE, remark VARCHAR(255), n_rid INTEGER, legacy_90702_rid INTEGER, amount NUMERIC(18,4), created_source_at TIMESTAMP WITH TIME ZONE, legacy_90700_rid INTEGER, legacy_90699_rid INTEGER, quantity INTEGER,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_task_upd ON TIME.ls_task(upd);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_task_primary_cost_unit_dbk ON TIME.ls_task(primary_cost_unit_dbk);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_person_task_assignment (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
task_dbk VARCHAR(24), person_dbk VARCHAR(24), cost_unit_dbk VARCHAR(24), rtype VARCHAR(1), last_work TIMESTAMP WITH TIME ZONE, effort_plan NUMERIC(19,3),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_person_task_assignment_upd ON TIME.ls_person_task_assignment(upd);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_time_recording (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
time_entry_id UUID UNIQUE REFERENCES TIME.time_entry(id) ON DELETE SET NULL,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
person_dbk VARCHAR(24), lfdnr INTEGER, record_type VARCHAR(32), mcl_id VARCHAR(255), mcl_desc VARCHAR(8000), recorded_from TIMESTAMP WITH TIME ZONE, recorded_to TIMESTAMP WITH TIME ZONE, effort NUMERIC(19,3), remark VARCHAR(8000), url VARCHAR(1000), activity_type_id INTEGER,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_time_recording_upd ON TIME.ls_time_recording(upd);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TIME.ls_time_recording_assignment (
|
||||
dbk VARCHAR(24) PRIMARY KEY,
|
||||
dbk_uid VARCHAR(7), upd VARCHAR(24), upd_anz INTEGER, upd_uid VARCHAR(7), status INTEGER,
|
||||
person_task_assignment_dbk VARCHAR(24), time_recording_dbk VARCHAR(24), mcl_desc VARCHAR(8000), effort NUMERIC(19,3), remark VARCHAR(8000),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ls_time_recording_assignment_upd ON TIME.ls_time_recording_assignment(upd);
|
||||
|
|
@ -34,7 +34,6 @@ $$;
|
|||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint c
|
||||
JOIN pg_class r ON r.oid = c.conrelid
|
||||
JOIN pg_namespace n ON n.oid = r.relnamespace
|
||||
|
|
@ -48,7 +47,7 @@ BEGIN
|
|||
CHECK (
|
||||
document_type IN (
|
||||
'TED_PACKAGE', 'TED_NOTICE', 'EMAIL', 'MIME_MESSAGE', 'PDF', 'DOCX', 'HTML',
|
||||
'XML_GENERIC', 'TEXT', 'MARKDOWN', 'ZIP_ARCHIVE', 'GENERIC_BINARY', 'UNKNOWN'
|
||||
'XML_GENERIC', 'TEXT', 'MARKDOWN', 'ZIP_ARCHIVE', 'GENERIC_BINARY', 'TIME_ENTRY', 'UNKNOWN'
|
||||
)
|
||||
);
|
||||
END IF;
|
||||
|
|
|
|||
Loading…
Reference in New Issue