diff --git a/biz.ganttproject.core/src/main/java/biz/ganttproject/customproperty/CustomProperty.kt b/biz.ganttproject.core/src/main/java/biz/ganttproject/customproperty/CustomProperty.kt index 2e1c69a9ef..b6bcdd2b1f 100644 --- a/biz.ganttproject.core/src/main/java/biz/ganttproject/customproperty/CustomProperty.kt +++ b/biz.ganttproject.core/src/main/java/biz/ganttproject/customproperty/CustomProperty.kt @@ -119,12 +119,12 @@ enum class CustomPropertyClass(val iD: String, val javaClass: Class<*>) { class CustomPropertyEvent { val type: Int - var definition: CustomPropertyDefinition + var definition: CustomPropertyDefinition? = null private set var oldValue: CustomPropertyDefinition? = null private set - constructor(type: Int, definition: CustomPropertyDefinition) { + constructor(type: Int, definition: CustomPropertyDefinition?) { this.type = type this.definition = definition } diff --git a/cloud.ganttproject.colloboque/src/main/resources/database-schema-template.sql b/cloud.ganttproject.colloboque/src/main/resources/database-schema-template.sql index 7e02aa2ea8..3fb8f5b249 100644 --- a/cloud.ganttproject.colloboque/src/main/resources/database-schema-template.sql +++ b/cloud.ganttproject.colloboque/src/main/resources/database-schema-template.sql @@ -17,6 +17,7 @@ CREATE TABLE TaskDates( uid VARCHAR(128) PRIMARY KEY REFERENCES TaskName, start_date DATE NOT NULL, duration_days INT NOT NULL DEFAULT 1, + end_date DATE, earliest_start_date DATE ); @@ -42,7 +43,8 @@ CREATE TABLE TaskTextProperties( CREATE TABLE TaskCostProperties( uid VARCHAR(128) REFERENCES TaskName PRIMARY KEY, is_cost_calculated BOOLEAN, - cost_manual_value NUMERIC + cost_manual_value NUMERIC, + cost NUMERIC ); CREATE TABLE TaskClassProperties( @@ -58,10 +60,12 @@ SELECT TaskName.uid, num, name, start_date, + end_date, duration_days AS duration, earliest_start_date, is_cost_calculated, cost_manual_value, + cost, is_milestone, is_project_task, MAX(TIP.prop_value) FILTER (WHERE TIP.prop_name = 'completion') AS completion, diff --git a/ganttproject-tester/test/net/sourceforge/ganttproject/customProperty/CustomPropertyImportTest.java b/ganttproject-tester/test/net/sourceforge/ganttproject/customProperty/CustomPropertyImportTest.java deleted file mode 100644 index ae2f579c2c..0000000000 --- a/ganttproject-tester/test/net/sourceforge/ganttproject/customProperty/CustomPropertyImportTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.sourceforge.ganttproject.customProperty; - -import java.util.List; - -import biz.ganttproject.customproperty.CustomPropertyClass; -import biz.ganttproject.customproperty.CustomPropertyDefinition; -import junit.framework.TestCase; -import net.sourceforge.ganttproject.task.CustomColumnsManager; - -public class CustomPropertyImportTest extends TestCase { - public void testImportDuplicatedProperties() { - { - CustomColumnsManager target = new CustomColumnsManager(); - target.createDefinition(CustomPropertyClass.TEXT, "col1", null); - target.createDefinition(CustomPropertyClass.TEXT, "col2", null); - - CustomColumnsManager source = new CustomColumnsManager(); - source.createDefinition(CustomPropertyClass.TEXT, "col1", null); - source.createDefinition(CustomPropertyClass.TEXT, "col3", null); - - target.importData(source); - List definitions = target.getDefinitions(); - assertEquals(3, definitions.size()); - } - { - CustomColumnsManager target = new CustomColumnsManager(); - target.createDefinition(CustomPropertyClass.TEXT, "col1", null); - target.createDefinition(CustomPropertyClass.TEXT, "col2", null); - - CustomColumnsManager source = new CustomColumnsManager(); - source.createDefinition(CustomPropertyClass.DATE, "col1", null); - source.createDefinition(CustomPropertyClass.TEXT, "col3", null); - - target.importData(source); - List definitions = target.getDefinitions(); - assertEquals(4, definitions.size()); - } - } -} diff --git a/ganttproject-tester/test/net/sourceforge/ganttproject/customProperty/CustomPropertyImportTest.kt b/ganttproject-tester/test/net/sourceforge/ganttproject/customProperty/CustomPropertyImportTest.kt new file mode 100644 index 0000000000..68625454c8 --- /dev/null +++ b/ganttproject-tester/test/net/sourceforge/ganttproject/customProperty/CustomPropertyImportTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2024 BarD Software s.r.o., Dmitry Barashev. + * + * This file is part of GanttProject, an opensource project management tool. + * + * GanttProject is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GanttProject is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GanttProject. If not, see . + */ +package net.sourceforge.ganttproject.customProperty + +import biz.ganttproject.customproperty.CustomPropertyClass +import biz.ganttproject.customproperty.CustomPropertyEvent +import biz.ganttproject.customproperty.CustomPropertyListener +import net.sourceforge.ganttproject.task.CustomColumnsManager +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CustomPropertyImportTest { + @Test + fun testImportDuplicatedProperties() { + run { + val target = CustomColumnsManager() + target.createDefinition(CustomPropertyClass.TEXT, "col1", null) + target.createDefinition(CustomPropertyClass.TEXT, "col2", null) + + val source = CustomColumnsManager() + source.createDefinition(CustomPropertyClass.TEXT, "col1", null) + source.createDefinition(CustomPropertyClass.TEXT, "col3", null) + + target.importData(source) + val definitions = target.definitions + assertEquals(3, definitions.size) + } + run { + val target = CustomColumnsManager() + target.createDefinition(CustomPropertyClass.TEXT, "col1", null) + target.createDefinition(CustomPropertyClass.TEXT, "col2", null) + + val source = CustomColumnsManager() + source.createDefinition(CustomPropertyClass.DATE, "col1", null) + source.createDefinition(CustomPropertyClass.TEXT, "col3", null) + + target.importData(source) + val definitions = target.definitions + assertEquals(4, definitions.size) + } + } + + @Test + fun `import preserves property id`() { + val target = CustomColumnsManager() + target.createDefinition("col1", CustomPropertyClass.TEXT.iD, "col1", null) + target.createDefinition("col3", CustomPropertyClass.TEXT.iD, "col3", null) + + val source = CustomColumnsManager() + source.createDefinition("col2", CustomPropertyClass.TEXT.iD, "col2", null) + source.createDefinition("col3", CustomPropertyClass.TEXT.iD, "Column3", null) + + target.importData(source) + val definitions = target.definitions + assertEquals(4, definitions.size) + + assertEquals(setOf("col1", "col2", "col3", "tpc0"), definitions.map { it.id }.toSet()) + } + + @Test + fun `import triggers only one event`() { + val target = CustomColumnsManager() + var eventCount = 0 + target.addListener(object: CustomPropertyListener { + override fun customPropertyChange(event: CustomPropertyEvent) { + eventCount++ + } + }) + + val source = CustomColumnsManager() + source.createDefinition(CustomPropertyClass.DATE, "col1", null) + source.createDefinition(CustomPropertyClass.TEXT, "col3", null) + + target.importData(source) + assertEquals(1, eventCount) + } +} diff --git a/ganttproject/src/main/java/biz/ganttproject/impex/csv/GanttCSVOpen.java b/ganttproject/src/main/java/biz/ganttproject/impex/csv/GanttCSVOpen.java index 8d713cce4b..4c936e573c 100644 --- a/ganttproject/src/main/java/biz/ganttproject/impex/csv/GanttCSVOpen.java +++ b/ganttproject/src/main/java/biz/ganttproject/impex/csv/GanttCSVOpen.java @@ -107,12 +107,6 @@ private static RecordGroup createTaskRecordGroup(final TaskManager taskManager, return new TaskRecords(taskManager, resourceManager, timeUnitStack); } - protected static void createCustomProperties(Collection customFields, CustomPropertyManager customPropertyManager) { - for (String name : customFields) { - customPropertyManager.createDefinition(name, CustomPropertyClass.TEXT.getID(), name, null); - } - } - private static RecordGroup createResourceRecordGroup(HumanResourceManager resourceManager, RoleManager roleManager) { return resourceManager == null ? null : new ResourceRecords(resourceManager, roleManager); } diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/parser/TaskSerializer.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/parser/TaskSerializer.kt index f517de8c26..4487c51906 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/parser/TaskSerializer.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/parser/TaskSerializer.kt @@ -38,6 +38,7 @@ import com.google.common.xml.XmlEscapers import net.sourceforge.ganttproject.GPLogger import net.sourceforge.ganttproject.gui.zoom.ZoomManager import net.sourceforge.ganttproject.task.CostStub +import net.sourceforge.ganttproject.task.CustomColumnsManager import net.sourceforge.ganttproject.task.Task import net.sourceforge.ganttproject.task.TaskManager import net.sourceforge.ganttproject.task.TaskView @@ -59,13 +60,15 @@ class TaskLoader(private val taskManager: TaskManager, private val treeCollapseV private val mapXmlGantt = mutableMapOf() fun loadTaskCustomPropertyDefinitions(xmlProject: XmlProject) { + val bufferCustomPropertyManager = CustomColumnsManager() xmlProject.tasks.taskproperties?.filter { it.type == "custom" }?.forEach { xmlTaskProperty -> - val def = taskManager.customPropertyManager.createDefinition( + val def = bufferCustomPropertyManager.createDefinition( xmlTaskProperty.id, xmlTaskProperty.valuetype, xmlTaskProperty.name, xmlTaskProperty.defaultvalue) xmlTaskProperty.simpleSelect?.let { def.calculationMethod = SimpleSelect(propertyId = xmlTaskProperty.id, selectExpression = StringEscapeUtils.unescapeXml(it.select), resultClass = def.propertyClass.javaClass) } } + taskManager.customPropertyManager.importData(bufferCustomPropertyManager) } fun loadTask(parent: XmlTask?, child: XmlTask): Task { diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDataLoader.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDataLoader.kt index daf168eff2..daed8405b7 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDataLoader.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDataLoader.kt @@ -47,12 +47,14 @@ fun buildInsertTaskQuery(dsl: DSLContext, task: Task): Insert { .set(Tables.TASK.IS_MILESTONE, task.isLegacyMilestone) .set(Tables.TASK.IS_PROJECT_TASK, task.isProjectTask) .set(Tables.TASK.START_DATE, task.start.toLocalDate()) + .set(Tables.TASK.END_DATE, task.end.toLocalDate()) .set(Tables.TASK.DURATION, task.duration.length) .set(Tables.TASK.COMPLETION, task.completionPercentage) .set(Tables.TASK.EARLIEST_START_DATE, task.third?.toLocalDate()) .set(Tables.TASK.PRIORITY, task.priority.persistentValue) .set(Tables.TASK.WEB_LINK, task.externalizedWebLink()) .set(Tables.TASK.COST_MANUAL_VALUE, costManualValue) + .set(Tables.TASK.COST, task.cost.value) .set(Tables.TASK.IS_COST_CALCULATED, isCostCalculated) .set(Tables.TASK.NOTES, task.externalizedNotes()) val customProps = mutableMapOf() @@ -86,6 +88,7 @@ fun buildInsertTaskDto(task: Task): OperationDto.InsertOperationDto { Tables.TASK.IS_MILESTONE.name to task.isLegacyMilestone.toString(), Tables.TASK.IS_PROJECT_TASK.name to task.isProjectTask.toString(), Tables.TASK.START_DATE.name to task.start.toLocalDate().toString(), + Tables.TASK.END_DATE.name to task.end.toLocalDate().toString(), Tables.TASK.DURATION.name to task.duration.length.toString(), Tables.TASK.COMPLETION.name to task.completionPercentage.toString(), Tables.TASK.EARLIEST_START_DATE.name to (task.third?.toLocalDate()?.toString()), @@ -93,6 +96,7 @@ fun buildInsertTaskDto(task: Task): OperationDto.InsertOperationDto { Tables.TASK.WEB_LINK.name to task.externalizedWebLink(), Tables.TASK.COST_MANUAL_VALUE.name to (costManualValue?.toString()), Tables.TASK.IS_COST_CALCULATED.name to (isCostCalculated?.toString()), + Tables.TASK.COST.name to task.cost.value?.toString(), Tables.TASK.NOTES.name to task.externalizedNotes(), ) ) diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDatabase.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDatabase.kt index 994e9b6655..ae4ba05b91 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDatabase.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/ProjectDatabase.kt @@ -99,6 +99,7 @@ interface ProjectDatabase { fun setProjectTask(oldValue: Boolean, newValue: Boolean) fun setShape(oldValue: ShapePaint?, newValue: ShapePaint?) fun setStart(oldValue: GanttCalendar, newValue: GanttCalendar) + fun setEnd(oldValue: GanttCalendar?, newValue: GanttCalendar) fun setWebLink(oldValue: String?, newValue: String?) fun interface Factory { diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/SqlProjectDatabaseImpl.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/SqlProjectDatabaseImpl.kt index 0fe472bd94..75ed3357f1 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/SqlProjectDatabaseImpl.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/storage/SqlProjectDatabaseImpl.kt @@ -489,6 +489,10 @@ class SqlTaskUpdateBuilder(private val task: Task, override fun setStart(oldValue: GanttCalendar, newValue: GanttCalendar) = appendUpdate(TASK.START_DATE, oldValue.toLocalDate(), newValue.toLocalDate()) + override fun setEnd(oldValue: GanttCalendar?, newValue: GanttCalendar) { + appendUpdate(TASK.END_DATE, oldValue?.toLocalDate(), newValue.toLocalDate()) + } + override fun setDuration(oldValue: TimeDuration, newValue: TimeDuration) = appendUpdate(TASK.DURATION, oldValue.length, newValue.length) @@ -504,6 +508,7 @@ class SqlTaskUpdateBuilder(private val task: Task, override fun setCost(oldValue: Task.Cost, newValue: Task.Cost) { appendUpdate(TASK.IS_COST_CALCULATED, oldValue.isCalculated, newValue.isCalculated) appendUpdate(TASK.COST_MANUAL_VALUE, oldValue.manualValue, newValue.manualValue) + appendUpdate(TASK.COST, oldValue.value, newValue.value) } override fun setCustomProperties( diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/task/CustomColumnsManager.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/task/CustomColumnsManager.kt index d606b9e929..4a2acfccd3 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/task/CustomColumnsManager.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/task/CustomColumnsManager.kt @@ -32,14 +32,17 @@ class CustomColumnsManager : CustomPropertyManager { private val listeners = mutableListOf() private val mapIdCustomColum = mutableMapOf() private var nextId = 0 + private var isImporting = false - private fun addNewCustomColumn(customColumn: CustomColumn) { + private fun addNewCustomColumn(customColumn: CustomColumn, fireChange: Boolean) { if (mapIdCustomColum[customColumn.id] != null) { throw CustomColumnsException(CustomColumnsException.ALREADY_EXIST, "Column with ID=${customColumn.id} is already registered") } mapIdCustomColum[customColumn.id] = customColumn - val event = CustomPropertyEvent(CustomPropertyEvent.EVENT_ADD, customColumn) - fireCustomColumnsChange(event) + if (fireChange) { + val event = CustomPropertyEvent(CustomPropertyEvent.EVENT_ADD, customColumn) + fireCustomColumnsChange(event) + } } override fun addListener(listener: CustomPropertyListener) { @@ -56,7 +59,7 @@ class CustomColumnsManager : CustomPropertyManager { val stub = decodeTypeAndDefaultValue(typeAsString, defaultValueAsString) val result = CustomColumn(this, name, stub.propertyClass, stub.defaultValue) result.id = id - addNewCustomColumn(result) + addNewCustomColumn(result, true) return result } @@ -64,24 +67,32 @@ class CustomColumnsManager : CustomPropertyManager { val stub = create(propertyClass, defValue) val result = CustomColumn(this, colName, stub.propertyClass, stub.defaultValue) result.id = createId() - addNewCustomColumn(result) + addNewCustomColumn(result, true) return result } - override fun importData(source: CustomPropertyManager): Map { - val result = mutableMapOf() - for (thatColumn in source.definitions) { - var thisColumn = findByName(thatColumn.name) - if (thisColumn == null || thisColumn.propertyClass != thatColumn.propertyClass) { - thisColumn = CustomColumn(this, thatColumn.name, thatColumn.propertyClass, thatColumn.defaultValue) - thisColumn.id = createId() - thisColumn.attributes.putAll(thatColumn.attributes) - addNewCustomColumn(thisColumn) + override fun importData(source: CustomPropertyManager): Map = + try { + isImporting = true + val result = mutableMapOf() + for (thatColumn in source.definitions) { + var thisColumn = findByName(thatColumn.name) + if (thisColumn == null || thisColumn.propertyClass != thatColumn.propertyClass) { + thisColumn = CustomColumn(this, thatColumn.name, thatColumn.propertyClass, thatColumn.defaultValue) + thisColumn.id = findById(thatColumn.id)?.let { createId() } ?: thatColumn.id + thisColumn.attributes.putAll(thatColumn.attributes) + thisColumn.calculationMethod = thatColumn.calculationMethod + addNewCustomColumn(thisColumn, false) + } + result[thatColumn] = thisColumn } - result[thatColumn] = thisColumn + result + } finally { + isImporting = false + val event = CustomPropertyEvent(CustomPropertyEvent.EVENT_REBUILD, null) + fireCustomColumnsChange(event) } - return result - } + override fun getCustomPropertyDefinition(id: String): CustomPropertyDefinition? { return mapIdCustomColum[id] @@ -99,21 +110,26 @@ class CustomColumnsManager : CustomPropertyManager { } private fun fireCustomColumnsChange(event: CustomPropertyEvent) { - listeners.forEach { - try { - it.customPropertyChange(event) - } catch (ex: Exception) { - LOG.error("Failure when processing custom columns event", exception = ex) + if (!isImporting) { + listeners.forEach { + try { + it.customPropertyChange(event) + } catch (ex: Exception) { + LOG.error("Failure when processing custom columns event", exception = ex) + } } } } fun fireDefinitionChanged(event: Int, def: CustomColumn, oldDef: CustomColumn) { - val e = CustomPropertyEvent(event, def, oldDef) - fireCustomColumnsChange(e) + if (!isImporting) { + val e = CustomPropertyEvent(event, def, oldDef) + fireCustomColumnsChange(e) + } } private fun findByName(name: String) = mapIdCustomColum.values.find { it.name == name } + private fun findById(id: String) = mapIdCustomColum.values.find { it.id == id } private fun createId(): String { while (true) { val id = "$ID_PREFIX${nextId++}" diff --git a/ganttproject/src/main/java/net/sourceforge/ganttproject/task/TaskImpl.kt b/ganttproject/src/main/java/net/sourceforge/ganttproject/task/TaskImpl.kt index 19306d605e..ecce89cdde 100644 --- a/ganttproject/src/main/java/net/sourceforge/ganttproject/task/TaskImpl.kt +++ b/ganttproject/src/main/java/net/sourceforge/ganttproject/task/TaskImpl.kt @@ -223,6 +223,9 @@ internal open class MutatorImpl( if (taskImpl.duration != myDurationChange.oldValue) { taskUpdateBuilder.setDuration(myDurationChange.oldValue, taskImpl.duration) } + if (taskImpl.end != myEndChange.oldValue) { + taskUpdateBuilder.setEnd(myEndChange.oldValue, taskImpl.end) + } } } diff --git a/ganttproject/src/main/resources/resources/sql/init-project-database-step2.sql b/ganttproject/src/main/resources/resources/sql/init-project-database-step2.sql index 14b9ebf8d2..57657884e8 100644 --- a/ganttproject/src/main/resources/resources/sql/init-project-database-step2.sql +++ b/ganttproject/src/main/resources/resources/sql/init-project-database-step2.sql @@ -1,30 +1,30 @@ -CREATE ALIAS IF NOT EXISTS TASK_COST FOR "net.sourceforge.ganttproject.storage.H2FunctionsKt.taskCost"; -CREATE ALIAS IF NOT EXISTS TASK_END_DATE FOR "net.sourceforge.ganttproject.storage.H2FunctionsKt.taskEndDate"; +-- CREATE ALIAS IF NOT EXISTS TASK_COST FOR "net.sourceforge.ganttproject.storage.H2FunctionsKt.taskCost"; +-- CREATE ALIAS IF NOT EXISTS TASK_END_DATE FOR "net.sourceforge.ganttproject.storage.H2FunctionsKt.taskEndDate"; +-- +-- DROP VIEW TaskViewForComputedColumns; +-- DROP TABLE Task CASCADE ; +-- create table if not exists Task ( +-- uid varchar not null, +-- num integer not null, +-- name varchar not null, +-- color varchar null, +-- shape varchar null, +-- is_milestone boolean not null DEFAULT false, +-- is_project_task boolean not null DEFAULT false, +-- start_date date not null, +-- end_date date null, +-- duration integer not null, +-- completion integer null, +-- earliest_start_date date null, +-- priority varchar not null DEFAULT '1', +-- web_link varchar null, +-- cost_manual_value numeric(1000, 2) null, +-- is_cost_calculated boolean null, +-- notes varchar null, +-- cost numeric(1000, 2) null, +-- +-- primary key (uid) +-- ); -DROP VIEW TaskViewForComputedColumns; -DROP TABLE Task CASCADE ; -create table if not exists Task ( - uid varchar not null, - num integer not null, - name varchar not null, - color varchar null, - shape varchar null, - is_milestone boolean not null DEFAULT false, - is_project_task boolean not null DEFAULT false, - start_date date not null, - end_date date GENERATED ALWAYS AS TASK_END_DATE(num), - duration integer not null, - completion integer null, - earliest_start_date date null, - priority varchar not null DEFAULT '1', - web_link varchar null, - cost_manual_value numeric(1000, 2) null, - is_cost_calculated boolean null, - notes varchar null, - cost numeric(1000, 2) GENERATED ALWAYS AS TASK_COST(num), - - primary key (uid) -); - -ALTER TABLE TaskDependency ADD CONSTRAINT dependee_fk FOREIGN KEY (dependee_uid) REFERENCES Task ON DELETE CASCADE ; -ALTER TABLE TaskDependency ADD CONSTRAINT dependant_fk FOREIGN KEY (dependant_uid) REFERENCES Task ON DELETE CASCADE ; \ No newline at end of file +-- ALTER TABLE TaskDependency ADD CONSTRAINT dependee_fk FOREIGN KEY (dependee_uid) REFERENCES Task ON DELETE CASCADE ; +-- ALTER TABLE TaskDependency ADD CONSTRAINT dependant_fk FOREIGN KEY (dependant_uid) REFERENCES Task ON DELETE CASCADE ; \ No newline at end of file diff --git a/ganttproject/src/main/resources/resources/sql/init-project-database.sql b/ganttproject/src/main/resources/resources/sql/init-project-database.sql index c9152105b3..063a065b38 100644 --- a/ganttproject/src/main/resources/resources/sql/init-project-database.sql +++ b/ganttproject/src/main/resources/resources/sql/init-project-database.sql @@ -36,8 +36,8 @@ create table if not exists TaskDependency ( hardness varchar not null, primary key (dependee_uid, dependant_uid), --- foreign key (dependee_uid) references Task(uid), --- foreign key (dependant_uid) references Task(uid), + foreign key (dependee_uid) references Task(uid), + foreign key (dependant_uid) references Task(uid), check (dependee_uid <> dependant_uid) ); diff --git a/ganttproject/src/test/java/biz/ganttproject/storage/ProjectDatabaseTest.kt b/ganttproject/src/test/java/biz/ganttproject/storage/ProjectDatabaseTest.kt index aca2cb5962..c0460e8b1a 100644 --- a/ganttproject/src/test/java/biz/ganttproject/storage/ProjectDatabaseTest.kt +++ b/ganttproject/src/test/java/biz/ganttproject/storage/ProjectDatabaseTest.kt @@ -114,6 +114,7 @@ class ProjectDatabaseTest { assertEquals(tasks[0].isMilestone, false) assertEquals(tasks[0].isProjectTask, true) assertEquals(tasks[0].startDate.toIsoNoHours(), task.start.toXMLString()) + assertEquals(tasks[0].endDate.toIsoNoHours(), task.end.toXMLString()) assertEquals(tasks[0].duration, 10) assertEquals(tasks[0].completion, 20) assertEquals(tasks[0].earliestStartDate.toIsoNoHours(), task.third.toXMLString()) @@ -121,6 +122,7 @@ class ProjectDatabaseTest { assertEquals(tasks[0].webLink, "love-testing.com") assertEquals(tasks[0].costManualValue.toDouble(), 666.7) assertEquals(tasks[0].isCostCalculated, true) + assertEquals(tasks[0].cost, BigDecimal.valueOf(0.0).setScale(2)) assertEquals(tasks[0].notes, "abacaba") val txns = projectDatabase.fetchTransactions(limit = 10) @@ -221,6 +223,7 @@ class ProjectDatabaseTest { projectDatabase.init() val startDateBefore = CalendarFactory.createGanttCalendar(2022, 4, 3) val startDateAfter = CalendarFactory.createGanttCalendar(2025, 7, 13) + val endDateAfter = CalendarFactory.createGanttCalendar(2025, 7, 14) val task = taskManager .newTaskBuilder() .withUid("someuid") @@ -241,6 +244,7 @@ class ProjectDatabaseTest { assertEquals(tasks[0].num, 1) assertEquals(tasks[0].name, "Name2") assertEquals(tasks[0].startDate.toIsoNoHours(), startDateAfter.toXMLString()) + assertEquals(tasks[0].endDate.toIsoNoHours(), endDateAfter.toXMLString()) assertNotEquals(tasks[0].startDate.toIsoNoHours(), startDateBefore.toXMLString()) val txns = projectDatabase.fetchTransactions(limit = 10) @@ -418,6 +422,7 @@ class ProjectDatabaseTest { when (val stmt = txns[0].colloboqueOperations[0]) { is OperationDto.UpdateOperationDto -> { assert(stmt.newValues.containsKey("duration")) { "Statement dto is: $stmt" } + assert(stmt.newValues.containsKey("end_date")) { "Statement dto is: $stmt" } } else -> { fail("Wrong type of operation. Operation dto: $stmt") @@ -470,6 +475,7 @@ class ProjectDatabaseTest { assert(stmt.newValues.containsKey("completion") && stmt.newValues["completion"] == "50") assert(stmt.newValues.containsKey("is_cost_calculated") && stmt.newValues["is_cost_calculated"] == "false") assert(stmt.newValues.containsKey("cost_manual_value") && stmt.newValues["cost_manual_value"] == "10") + assert(stmt.newValues.containsKey("cost") && stmt.newValues["cost"] == "10") assertFalse(stmt.newValues.containsKey("expand")) assertFalse(stmt.newValues.containsKey("start_date")) assertFalse(stmt.newValues.containsKey("expiration"))