Update
This commit is contained in:
1 parent
e1456ed4ca
commit
fc0dfd0556
3 files changed
+85
-23
No files matched your search
+74
-20
@@ -4230,6 +4230,16 @@ function addSyncChange(changes: ZammadSyncChange[], field: string, from: unknown
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const informationalZammadSyncChangeFields = new Set([
|
||||||
|
"Fremdes Ticket",
|
||||||
|
"Fremder Zammad-Owner",
|
||||||
|
"Hinweis"
|
||||||
|
]);
|
||||||
|
|
||||||
|
function zammadSyncResultChanges(changes: ZammadSyncChange[]) {
|
||||||
|
return changes.some((change) => !informationalZammadSyncChangeFields.has(change.field)) ? changes : [];
|
||||||
|
}
|
||||||
|
|
||||||
function zammadSyncSnapshot(zammadTicket: ZammadTicketImport) {
|
function zammadSyncSnapshot(zammadTicket: ZammadTicketImport) {
|
||||||
return {
|
return {
|
||||||
ticketId: zammadTicket.zammadTicketId,
|
ticketId: zammadTicket.zammadTicketId,
|
||||||
@@ -5161,7 +5171,9 @@ async function planExistingZammadTicketSync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (zammadTicket.workType === "support" && zammadTicket.zammadStateId === 4) {
|
if (zammadTicket.workType === "support" && zammadTicket.zammadStateId === 4) {
|
||||||
return changes.length > 0
|
const resultChanges = zammadSyncResultChanges(changes);
|
||||||
|
|
||||||
|
return resultChanges.length > 0
|
||||||
? {
|
? {
|
||||||
ticketNumber: localTicket.ticket_number,
|
ticketNumber: localTicket.ticket_number,
|
||||||
action: "update",
|
action: "update",
|
||||||
@@ -5172,7 +5184,7 @@ async function planExistingZammadTicketSync(
|
|||||||
: "Zammad-Metadaten werden aktualisiert.",
|
: "Zammad-Metadaten werden aktualisiert.",
|
||||||
local: localSyncSnapshot(localTicket, null, { zammadPendingTodoExists: pendingTodoExists }),
|
local: localSyncSnapshot(localTicket, null, { zammadPendingTodoExists: pendingTodoExists }),
|
||||||
zammad: zammadSyncSnapshot(zammadTicket),
|
zammad: zammadSyncSnapshot(zammadTicket),
|
||||||
changes
|
changes: resultChanges
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -5208,17 +5220,19 @@ async function planExistingZammadTicketSync(
|
|||||||
changes.push({ field: "Projekt/Teamspace-Daten", from: "vorhanden", to: "werden gelöscht" });
|
changes.push({ field: "Projekt/Teamspace-Daten", from: "vorhanden", to: "werden gelöscht" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resultChanges = zammadSyncResultChanges(changes);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ticketNumber: localTicket.ticket_number,
|
ticketNumber: localTicket.ticket_number,
|
||||||
action: changes.length > 0 ? "update" : "skip",
|
action: resultChanges.length > 0 ? "update" : "skip",
|
||||||
reason: zammadTicket.ticketNumber !== localTicket.ticket_number
|
reason: zammadTicket.ticketNumber !== localTicket.ticket_number
|
||||||
? mergedTargetTicket
|
? mergedTargetTicket
|
||||||
? "Zammad-Merge wird lokal nachvollzogen; vorhandenes Ziel-Ticket wird verwendet."
|
? "Zammad-Merge wird lokal nachvollzogen; vorhandenes Ziel-Ticket wird verwendet."
|
||||||
: "Zammad-Merge wird lokal nachvollzogen; Ziel-Ticket wird synchronisiert und die Quelle umgehängt."
|
: "Zammad-Merge wird lokal nachvollzogen; Ziel-Ticket wird synchronisiert und die Quelle umgehängt."
|
||||||
: changes.length > 0 ? "Ticket wird als Support synchronisiert." : "Keine Änderung nach aktueller Auswahl.",
|
: resultChanges.length > 0 ? "Ticket wird als Support synchronisiert." : "Keine Änderung nach aktueller Auswahl.",
|
||||||
local: localSyncSnapshot(localTicket, null, { zammadPendingTodoExists: pendingTodoExists }),
|
local: localSyncSnapshot(localTicket, null, { zammadPendingTodoExists: pendingTodoExists }),
|
||||||
zammad: zammadSyncSnapshot(zammadTicket),
|
zammad: zammadSyncSnapshot(zammadTicket),
|
||||||
changes
|
changes: resultChanges
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5263,13 +5277,15 @@ async function planExistingZammadTicketSync(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resultChanges = zammadSyncResultChanges(changes);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ticketNumber: localTicket.ticket_number,
|
ticketNumber: localTicket.ticket_number,
|
||||||
action: changes.length > 0 ? "update" : "skip",
|
action: resultChanges.length > 0 ? "update" : "skip",
|
||||||
reason: changes.length > 0 ? "Ticket wird aktualisiert; Projektdaten bleiben wegen offener TS/TT-Prüfung lokal geschützt." : "Keine Änderung nach aktueller Auswahl.",
|
reason: resultChanges.length > 0 ? "Ticket wird aktualisiert; Projektdaten bleiben wegen offener TS/TT-Prüfung lokal geschützt." : "Keine Änderung nach aktueller Auswahl.",
|
||||||
local: localSyncSnapshot(localTicket, project, { zammadPendingTodoExists: pendingTodoExists }),
|
local: localSyncSnapshot(localTicket, project, { zammadPendingTodoExists: pendingTodoExists }),
|
||||||
zammad: zammadSyncSnapshot(zammadTicket),
|
zammad: zammadSyncSnapshot(zammadTicket),
|
||||||
changes
|
changes: resultChanges
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5277,7 +5293,7 @@ async function planExistingZammadTicketSync(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project) {
|
if (!project && fields.type && localTicket.work_type !== "consulting") {
|
||||||
changes.push({ field: "Projekt", from: null, to: "wird angelegt" });
|
changes.push({ field: "Projekt", from: null, to: "wird angelegt" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5327,17 +5343,19 @@ async function planExistingZammadTicketSync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resultChanges = zammadSyncResultChanges(changes);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ticketNumber: localTicket.ticket_number,
|
ticketNumber: localTicket.ticket_number,
|
||||||
action: changes.length > 0 ? "update" : "skip",
|
action: resultChanges.length > 0 ? "update" : "skip",
|
||||||
reason: zammadTicket.ticketNumber !== localTicket.ticket_number
|
reason: zammadTicket.ticketNumber !== localTicket.ticket_number
|
||||||
? mergedTargetTicket
|
? mergedTargetTicket
|
||||||
? "Zammad-Merge wird lokal nachvollzogen; vorhandenes Ziel-Ticket wird verwendet."
|
? "Zammad-Merge wird lokal nachvollzogen; vorhandenes Ziel-Ticket wird verwendet."
|
||||||
: "Zammad-Merge wird lokal nachvollzogen; Ziel-Ticket wird synchronisiert und die Quelle umgehängt."
|
: "Zammad-Merge wird lokal nachvollzogen; Ziel-Ticket wird synchronisiert und die Quelle umgehängt."
|
||||||
: changes.length > 0 ? "Ticket wird aktualisiert." : "Keine Änderung nach aktueller Auswahl.",
|
: resultChanges.length > 0 ? "Ticket wird aktualisiert." : "Keine Änderung nach aktueller Auswahl.",
|
||||||
local: localSyncSnapshot(localTicket, project, { zammadPendingTodoExists: pendingTodoExists }),
|
local: localSyncSnapshot(localTicket, project, { zammadPendingTodoExists: pendingTodoExists }),
|
||||||
zammad: zammadSyncSnapshot(zammadTicket),
|
zammad: zammadSyncSnapshot(zammadTicket),
|
||||||
changes
|
changes: resultChanges
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6669,6 +6687,7 @@ async function applyZammadTicketSync(
|
|||||||
const organization = shouldResolveOrganization ? await getOrganizationByZammadId(client, zammadTicket.organizationId) : null;
|
const organization = shouldResolveOrganization ? await getOrganizationByZammadId(client, zammadTicket.organizationId) : null;
|
||||||
const ownerMapping = await zammadOwnerUserMapping(client, zammadTicket.zammadOwnerId);
|
const ownerMapping = await zammadOwnerUserMapping(client, zammadTicket.zammadOwnerId);
|
||||||
const zammadOwnerUserId = ownerMapping.status === "ambiguous" ? localTicket.user_id : ownerMapping.userId;
|
const zammadOwnerUserId = ownerMapping.status === "ambiguous" ? localTicket.user_id : ownerMapping.userId;
|
||||||
|
const zammadStateChanged = numberOrNull(localTicket.zammad_state_id) !== zammadTicket.zammadStateId;
|
||||||
|
|
||||||
if (shouldResolveOrganization && !organization) {
|
if (shouldResolveOrganization && !organization) {
|
||||||
return "organization-missing" as const;
|
return "organization-missing" as const;
|
||||||
@@ -6754,6 +6773,12 @@ async function applyZammadTicketSync(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (zammadStateChanged) {
|
||||||
|
const completedAt = new Date();
|
||||||
|
await restartProjectFollowupPeriodForTicket(client, localTicket.id, completedAt);
|
||||||
|
await completeAutomaticProjectFollowupsForTicketStatusChange(client, localTicket.id, completedAt);
|
||||||
|
}
|
||||||
|
|
||||||
if (fields.pendingTodo) {
|
if (fields.pendingTodo) {
|
||||||
await ensureZammadPendingTodo(client, localTicket.id, userId, zammadTicket);
|
await ensureZammadPendingTodo(client, localTicket.id, userId, zammadTicket);
|
||||||
}
|
}
|
||||||
@@ -7778,6 +7803,35 @@ async function completeAutomaticProjectFollowupsForSession(client: QueryClient,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function completeAutomaticProjectFollowupsForTicketStatusChange(client: QueryClient, ticketId: string, completedAt: Date) {
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
UPDATE ticket_todos tt
|
||||||
|
SET status = 'done',
|
||||||
|
completed_at = COALESCE(tt.completed_at, $2::timestamptz),
|
||||||
|
updated_at = now()
|
||||||
|
FROM project_followup_reminders pfr
|
||||||
|
WHERE pfr.todo_id = tt.id
|
||||||
|
AND pfr.ticket_id = $1
|
||||||
|
AND tt.ticket_id = $1
|
||||||
|
AND tt.status = 'open';
|
||||||
|
`,
|
||||||
|
[ticketId, completedAt.toISOString()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restartProjectFollowupPeriodForTicket(client: QueryClient, ticketId: string, restartedAt: Date) {
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
UPDATE projects
|
||||||
|
SET updated_at = GREATEST(updated_at, $2::timestamptz)
|
||||||
|
WHERE ticket_id = $1
|
||||||
|
AND closed_at IS NULL;
|
||||||
|
`,
|
||||||
|
[ticketId, restartedAt.toISOString()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function getPeriodTicket(config: PeriodConfig, period: ParsedPeriod, ticketId: number, userId: string) {
|
async function getPeriodTicket(config: PeriodConfig, period: ParsedPeriod, ticketId: number, userId: string) {
|
||||||
await ensureRecurringSessionsForUserPeriod(userId, period.startIso, period.endIso);
|
await ensureRecurringSessionsForUserPeriod(userId, period.startIso, period.endIso);
|
||||||
|
|
||||||
@@ -12442,12 +12496,12 @@ app.patch("/api/projects/:projectId/review", requireUser, async (req, res) => {
|
|||||||
const updateResult = await client.query<{ id: string; ticket_id: string; close_review_required: boolean }>(
|
const updateResult = await client.query<{ id: string; ticket_id: string; close_review_required: boolean }>(
|
||||||
`
|
`
|
||||||
UPDATE projects
|
UPDATE projects
|
||||||
SET close_review_required = NOT $3::boolean,
|
SET close_review_required = NOT $2::boolean,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id::text, ticket_id::text, close_review_required;
|
RETURNING id::text, ticket_id::text, close_review_required;
|
||||||
`,
|
`,
|
||||||
[projectId, userId, acknowledged]
|
[projectId, acknowledged]
|
||||||
);
|
);
|
||||||
|
|
||||||
return updateResult.rows[0] ?? null;
|
return updateResult.rows[0] ?? null;
|
||||||
@@ -12493,13 +12547,13 @@ app.patch("/api/projects/:projectId/status", requireUser, async (req, res) => {
|
|||||||
const updateResult = await client.query<{ id: string; ticket_id: string; closed_at: string | null }>(
|
const updateResult = await client.query<{ id: string; ticket_id: string; closed_at: string | null }>(
|
||||||
`
|
`
|
||||||
UPDATE projects
|
UPDATE projects
|
||||||
SET closed_at = CASE WHEN $3::boolean THEN now() ELSE NULL END,
|
SET closed_at = CASE WHEN $2::boolean THEN now() ELSE NULL END,
|
||||||
close_review_required = CASE WHEN $3::boolean THEN false ELSE close_review_required END,
|
close_review_required = CASE WHEN $2::boolean THEN false ELSE close_review_required END,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id::text, ticket_id::text, closed_at;
|
RETURNING id::text, ticket_id::text, closed_at;
|
||||||
`,
|
`,
|
||||||
[projectId, userId, closed]
|
[projectId, closed]
|
||||||
);
|
);
|
||||||
|
|
||||||
return updateResult.rows[0] ?? null;
|
return updateResult.rows[0] ?? null;
|
||||||
|
|||||||
@@ -1315,7 +1315,7 @@ export function App() {
|
|||||||
|
|
||||||
setOpenTickets((items) => items.filter((item) => item.key !== ticket.key));
|
setOpenTickets((items) => items.filter((item) => item.key !== ticket.key));
|
||||||
|
|
||||||
if (pathWithoutSearch(path) === pathWithoutSearch(ticket.path)) {
|
if (route.page === "ticket" && route.ticketId === ticket.ticketId) {
|
||||||
const returnPath = current.returnPath && pathWithoutSearch(current.returnPath) !== pathWithoutSearch(ticket.path) ? current.returnPath : "/analysis";
|
const returnPath = current.returnPath && pathWithoutSearch(current.returnPath) !== pathWithoutSearch(ticket.path) ? current.returnPath : "/analysis";
|
||||||
routerNavigate(returnPath);
|
routerNavigate(returnPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -669,13 +669,21 @@ const preservedExistingPreviewChangeFields = new Set([
|
|||||||
"TicketTracker-Zusammenführung"
|
"TicketTracker-Zusammenführung"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const informationalExistingPreviewChangeFields = new Set([
|
||||||
|
"Fremdes Ticket",
|
||||||
|
"Fremder Zammad-Owner",
|
||||||
|
"Hinweis"
|
||||||
|
]);
|
||||||
|
|
||||||
function preserveExistingPreviewChanges(item: ZammadSyncItem, changes: ZammadSyncChange[], fields: ZammadSyncExistingFields) {
|
function preserveExistingPreviewChanges(item: ZammadSyncItem, changes: ZammadSyncChange[], fields: ZammadSyncExistingFields) {
|
||||||
const existingFields = new Set(changes.map((change) => change.field));
|
const existingFields = new Set(changes.map((change) => change.field));
|
||||||
|
|
||||||
return [
|
const resultChanges = [
|
||||||
...item.changes.filter((change) => preservedExistingPreviewChangeFields.has(change.field) && !existingFields.has(change.field) && changeAllowedByFields(item, change, fields)),
|
...item.changes.filter((change) => preservedExistingPreviewChangeFields.has(change.field) && !existingFields.has(change.field) && changeAllowedByFields(item, change, fields)),
|
||||||
...changes
|
...changes
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return resultChanges.some((change) => !informationalExistingPreviewChangeFields.has(change.field)) ? resultChanges : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function changesFromExistingPreviewCache(item: ZammadSyncItem, fields: ZammadSyncExistingFields) {
|
function changesFromExistingPreviewCache(item: ZammadSyncItem, fields: ZammadSyncExistingFields) {
|
||||||
@@ -737,7 +745,7 @@ function changesFromExistingPreviewCache(item: ZammadSyncItem, fields: ZammadSyn
|
|||||||
const consultingProjectCanBeTouched = !mergeProtected && zammadWorkType === "consulting" && (fields.type || localWorkType === "consulting");
|
const consultingProjectCanBeTouched = !mergeProtected && zammadWorkType === "consulting" && (fields.type || localWorkType === "consulting");
|
||||||
|
|
||||||
if (consultingProjectCanBeTouched) {
|
if (consultingProjectCanBeTouched) {
|
||||||
if (fields.type && !project) {
|
if (fields.type && localWorkType !== "consulting" && !project) {
|
||||||
changes.push({ field: "Projekt", from: null, to: "wird angelegt" });
|
changes.push({ field: "Projekt", from: null, to: "wird angelegt" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user