fix: transaction-safe AcceptSuggestionWith + safe eventIds fallback + debug logging
Root cause: s.eventIds may be undefined in JavaScript even when s.events has data (Wails v2 marshalling of []string in nested struct response). On calling AcceptSuggestionWith(eventIDs []string), empty array reached Go, no INSERTs executed, events silently lost. Changes: - Frontend: extractEventIds() fallback — s.eventIds || s.events[].id || [] - Frontend: console.log debug for eventIds/events in accept handler - Backend: AcceptSuggestionWith wrapped in tx (Begin/Commit/Rollback) so entry creation + event linking is atomic - Backend: AddWithSourceTx method for transaction-aware insert - Backend: buildEntry helper extracted - Backend: fmt.Printf debug logging for received eventIDs + link count - Backend: verification query after commit - Cleanup: removed stale frontend-dist assets, .gitignore build.log
This commit is contained in:
parent
7076980954
commit
21a595c3ce
|
|
@ -48,3 +48,4 @@ server-data/
|
|||
|
||||
# Build output
|
||||
build/
|
||||
build.log
|
||||
|
|
|
|||
|
|
@ -116,25 +116,57 @@ func (a *App) AcceptSuggestion(nodeID, summary string, minutes int, date string,
|
|||
return a.AcceptSuggestionWith(nodeID, summary, minutes, date, eventIDs)
|
||||
}
|
||||
|
||||
// AcceptSuggestionWith creates a worklog entry and links events. Uses flat fields to avoid Wails marshalling issues.
|
||||
// AcceptSuggestionWith creates a worklog entry and links events in a single transaction.
|
||||
// Uses flat fields to avoid Wails marshalling issues.
|
||||
func (a *App) AcceptSuggestionWith(nodeID, summary string, minutes int, date string, eventIDs []string) (*WorklogDTO, error) {
|
||||
d := date
|
||||
if d == "" {
|
||||
d = time.Now().Format("2006-01-02")
|
||||
}
|
||||
entry, err := a.worklog.AddWithSource(nodeID, summary, "", d, minutes, true, false, worklog.SourceSuggestion)
|
||||
|
||||
// Log what we received from the frontend
|
||||
fmt.Printf("DEBUG AcceptSuggestionWith: nodeID=%q summary=%q minutes=%d date=%q eventIDs=%v (len=%d)\n",
|
||||
nodeID, summary, minutes, d, eventIDs, len(eventIDs))
|
||||
|
||||
// Use a transaction to atomically create entry + link events
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("begin tx: %w", err)
|
||||
}
|
||||
// Link activity events to this worklog entry.
|
||||
defer tx.Rollback()
|
||||
|
||||
entry, err := a.worklog.AddWithSourceTx(tx, nodeID, summary, "", d, minutes, true, false, worklog.SourceSuggestion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create entry: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("DEBUG AcceptSuggestionWith: entry created id=%s\n", entry.ID)
|
||||
|
||||
linked := 0
|
||||
for _, eid := range eventIDs {
|
||||
_, err := a.db.Exec(
|
||||
res, err := tx.Exec(
|
||||
`INSERT OR IGNORE INTO worklog_entry_events (entry_id, event_id) VALUES (?,?)`,
|
||||
entry.ID, eid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("link event %s: %w", eid, err)
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
linked += int(n)
|
||||
}
|
||||
|
||||
fmt.Printf("DEBUG AcceptSuggestionWith: linked %d events (out of %d eventIDs)\n", linked, len(eventIDs))
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("commit tx: %w", err)
|
||||
}
|
||||
|
||||
// Verify the links were stored
|
||||
if len(eventIDs) > 0 {
|
||||
var count int
|
||||
a.db.QueryRow("SELECT COUNT(*) FROM worklog_entry_events WHERE entry_id = ?", entry.ID).Scan(&count)
|
||||
fmt.Printf("DEBUG AcceptSuggestionWith: verification COUNT(*) = %d\n", count)
|
||||
}
|
||||
|
||||
_ = a.sync.RecordOp(syncsvc.EntityWorklog, entry.ID, syncsvc.OpCreate, worklogPayload(entry))
|
||||
mins := 0
|
||||
if entry.Minutes != nil {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,7 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-cq32hqy7.js"></script>
|
||||
<script type="module" crossorigin src="/assets/main-DQ318Oic.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BafVhx43.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -970,16 +970,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
function extractEventIds(s) {
|
||||
if (s.eventIds && s.eventIds.length) return s.eventIds
|
||||
if (s.events && s.events.length) return s.events.map(ev => ev.id).filter(Boolean)
|
||||
return []
|
||||
}
|
||||
|
||||
async function acceptTodaySuggestion(s) {
|
||||
try {
|
||||
await wailsCall('AcceptSuggestionWith', s.nodeId, s.summary, s.suggestedMin, '', s.eventIds || [])
|
||||
const eventIds = extractEventIds(s)
|
||||
console.log('DEBUG acceptTodaySuggestion:', { nodeId: s.nodeId, eventIdsLen: eventIds.length, eventIds, eventsCount: s.events?.length })
|
||||
await wailsCall('AcceptSuggestionWith', s.nodeId, s.summary, s.suggestedMin, '', eventIds)
|
||||
await refreshAfterSuggestion()
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
async function acceptJournalSuggestion(s) {
|
||||
try {
|
||||
await wailsCall('AcceptSuggestionWith', s.nodeId, s.summary, s.suggestedMin, '', s.eventIds || [])
|
||||
const eventIds = extractEventIds(s)
|
||||
console.log('DEBUG acceptJournalSuggestion:', { nodeId: s.nodeId, eventIdsLen: eventIds.length, eventIds, eventsCount: s.events?.length })
|
||||
await wailsCall('AcceptSuggestionWith', s.nodeId, s.summary, s.suggestedMin, '', eventIds)
|
||||
await refreshAfterSuggestion()
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func (s *Service) Add(nodeID, summary, details string, minutes int, approximate,
|
|||
return s.AddWithSource(nodeID, summary, details, date, minutes, approximate, billable, SourceManual)
|
||||
}
|
||||
|
||||
// Add inserts a new worklog entry.
|
||||
// AddWithSource inserts a new worklog entry with an explicit source.
|
||||
func (s *Service) AddWithSource(nodeID, summary, details, date string, minutes int, approximate, billable bool, source string) (*Entry, error) {
|
||||
if nodeID == "" {
|
||||
return nil, fmt.Errorf("node_id required")
|
||||
|
|
@ -62,19 +62,7 @@ func (s *Service) AddWithSource(nodeID, summary, details, date string, minutes i
|
|||
date = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
e := &Entry{
|
||||
ID: util.UUID7(),
|
||||
NodeID: nodeID,
|
||||
Summary: summary,
|
||||
Details: details,
|
||||
Date: date,
|
||||
Minutes: &minutes,
|
||||
Approximate: approximate,
|
||||
Billable: billable,
|
||||
Source: source,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
e := buildEntry(nodeID, summary, details, date, minutes, approximate, billable, source)
|
||||
|
||||
_, err := s.db.Exec(
|
||||
`INSERT INTO worklog_entries (id,node_id,date,minutes,approximate,billable,
|
||||
|
|
@ -90,6 +78,50 @@ func (s *Service) AddWithSource(nodeID, summary, details, date string, minutes i
|
|||
return e, nil
|
||||
}
|
||||
|
||||
// AddWithSourceTx inserts a new worklog entry within an existing transaction.
|
||||
func (s *Service) AddWithSourceTx(tx *sql.Tx, nodeID, summary, details, date string, minutes int, approximate, billable bool, source string) (*Entry, error) {
|
||||
if nodeID == "" {
|
||||
return nil, fmt.Errorf("node_id required")
|
||||
}
|
||||
if summary == "" {
|
||||
return nil, fmt.Errorf("summary required")
|
||||
}
|
||||
if date == "" {
|
||||
date = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
e := buildEntry(nodeID, summary, details, date, minutes, approximate, billable, source)
|
||||
|
||||
_, err := tx.Exec(
|
||||
`INSERT INTO worklog_entries (id,node_id,date,minutes,approximate,billable,
|
||||
summary,details,source,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
e.ID, e.NodeID, e.Date, e.Minutes, boolInt(e.Approximate),
|
||||
boolInt(e.Billable), e.Summary, e.Details, e.Source,
|
||||
e.CreatedAt.Format(time.RFC3339), e.UpdatedAt.Format(time.RFC3339),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func buildEntry(nodeID, summary, details, date string, minutes int, approximate, billable bool, source string) *Entry {
|
||||
return &Entry{
|
||||
ID: util.UUID7(),
|
||||
NodeID: nodeID,
|
||||
Summary: summary,
|
||||
Details: details,
|
||||
Date: date,
|
||||
Minutes: &minutes,
|
||||
Approximate: approximate,
|
||||
Billable: billable,
|
||||
Source: source,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
// Update modifies an existing entry.
|
||||
func (s *Service) Update(id, summary, details string, minutes int, approximate, billable bool) error {
|
||||
t := time.Now().UTC().Format(time.RFC3339)
|
||||
|
|
|
|||
Loading…
Reference in New Issue