214 lines
6.1 KiB
Go
214 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
|
|
|
"verstak/internal/core/worklog"
|
|
syncsvc "verstak/internal/core/sync"
|
|
)
|
|
|
|
func (a *App) ListWorklog(nodeID string) ([]WorklogDTO, error) {
|
|
list, err := a.worklog.ListByNode(nodeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return toWorklogDTOs(list), nil
|
|
}
|
|
|
|
func (a *App) CreateWorklog(nodeID, summary string, minutes int) (*WorklogDTO, error) {
|
|
return a.CreateWorklogFull(nodeID, summary, "", "", minutes, false, false)
|
|
}
|
|
|
|
func (a *App) CreateWorklogFull(nodeID, summary, details, date string, minutes int, approximate, billable bool) (*WorklogDTO, error) {
|
|
if date == "" {
|
|
entry, err := a.worklog.Add(nodeID, summary, details, minutes, approximate, billable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = a.sync.RecordOp(syncsvc.EntityWorklog, entry.ID, syncsvc.OpCreate, worklogPayload(entry))
|
|
return entryToDTO(entry), nil
|
|
}
|
|
entry, err := a.worklog.AddWithDate(nodeID, summary, details, date, minutes, approximate, billable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = a.sync.RecordOp(syncsvc.EntityWorklog, entry.ID, syncsvc.OpCreate, worklogPayload(entry))
|
|
return entryToDTO(entry), nil
|
|
}
|
|
|
|
// --- report bindings ---
|
|
|
|
func (a *App) ListWorklogReport(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) ([]worklog.ReportRow, error) {
|
|
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
|
|
rows, err := a.worklog.ListReport(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a.worklog.BuildReportPaths(rows)
|
|
return rows, nil
|
|
}
|
|
|
|
func (a *App) WorklogReportSummary(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (*worklog.ReportSummary, error) {
|
|
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
|
|
return a.worklog.Summary(f)
|
|
}
|
|
|
|
func (a *App) ExportWorklogCSV(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (string, error) {
|
|
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
|
|
return a.worklog.ExportCSV(f)
|
|
}
|
|
|
|
func (a *App) ExportWorklogMarkdown(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (string, error) {
|
|
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
|
|
return a.worklog.ExportMarkdown(f)
|
|
}
|
|
|
|
func (a *App) ExportWorklogPDF(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) ([]byte, error) {
|
|
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
|
|
return a.worklog.ExportPDF(f)
|
|
}
|
|
|
|
func boolPtr(s string) *bool {
|
|
switch s {
|
|
case "yes":
|
|
v := true
|
|
return &v
|
|
case "no":
|
|
v := false
|
|
return &v
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func buildWorklogFilter(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) worklog.ReportFilter {
|
|
return worklog.ReportFilter{
|
|
DateFrom: dateFrom,
|
|
DateTo: dateTo,
|
|
NodeID: nodeID,
|
|
IncludeChildren: includeChildren,
|
|
Billable: boolPtr(billableFilter),
|
|
Approximate: boolPtr(approxFilter),
|
|
}
|
|
}
|
|
|
|
// SaveWorklogReport generates a worklog report and opens a SaveFileDialog.
|
|
func (a *App) SaveWorklogReport(format, dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (string, error) {
|
|
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
|
|
|
|
var data []byte
|
|
var ext string
|
|
switch format {
|
|
case "csv":
|
|
s, err := a.worklog.ExportCSV(f)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data = []byte(s)
|
|
ext = ".csv"
|
|
case "markdown":
|
|
s, err := a.worklog.ExportMarkdown(f)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data = []byte(s)
|
|
ext = ".md"
|
|
case "pdf":
|
|
var err error
|
|
data, err = a.worklog.ExportPDF(f)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ext = ".pdf"
|
|
default:
|
|
return "", fmt.Errorf("unknown format: %s", format)
|
|
}
|
|
|
|
from := dateFrom
|
|
if from == "" {
|
|
from = "all"
|
|
}
|
|
to := dateTo
|
|
if to == "" {
|
|
to = "all"
|
|
}
|
|
defaultName := fmt.Sprintf("verstak-worklog-%s--%s%s", from, to, ext)
|
|
|
|
path, err := wailsruntime.SaveFileDialog(a.ctx, wailsruntime.SaveDialogOptions{
|
|
DefaultFilename: defaultName,
|
|
Filters: []wailsruntime.FileFilter{
|
|
{DisplayName: format, Pattern: "*" + ext},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if path == "" {
|
|
return "", fmt.Errorf("отменено пользователем")
|
|
}
|
|
|
|
if err := os.WriteFile(path, data, 0o644); err != nil {
|
|
return "", fmt.Errorf("не удалось сохранить файл: %w", err)
|
|
}
|
|
|
|
return fmt.Sprintf("Отчёт сохранён: %s", path), nil
|
|
}
|
|
|
|
// GetWorklogEntryEvents returns activity events linked to a worklog entry.
|
|
func (a *App) GetWorklogEntryEvents(entryID string) ([]EventDTO, error) {
|
|
rows, err := a.db.Query(
|
|
`SELECT e.id, e.node_id, e.event_type, e.target_type, e.target_id, e.target_path,
|
|
e.title, COALESCE(e.metadata,''), e.created_at
|
|
FROM activity_events e
|
|
JOIN worklog_entry_events wle ON wle.event_id = e.id
|
|
WHERE wle.entry_id = ?
|
|
ORDER BY e.created_at ASC`, entryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []EventDTO
|
|
for rows.Next() {
|
|
var d EventDTO
|
|
if err := rows.Scan(&d.ID, &d.NodeID, &d.EventType, &d.TargetType,
|
|
&d.TargetID, &d.TargetPath, &d.Title, &d.DetailsJSON, &d.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, d)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// --- helpers ---
|
|
|
|
func toWorklogDTOs(list []worklog.Entry) []WorklogDTO {
|
|
result := make([]WorklogDTO, len(list))
|
|
for i := range list {
|
|
result[i] = *entryToDTO(&list[i])
|
|
}
|
|
return result
|
|
}
|
|
|
|
func entryToDTO(e *worklog.Entry) *WorklogDTO {
|
|
mins := 0
|
|
if e.Minutes != nil {
|
|
mins = *e.Minutes
|
|
}
|
|
return &WorklogDTO{
|
|
ID: e.ID,
|
|
NodeID: e.NodeID,
|
|
Summary: e.Summary,
|
|
Minutes: mins,
|
|
Date: e.Date,
|
|
Details: e.Details,
|
|
Approximate: e.Approximate,
|
|
Billable: e.Billable,
|
|
Source: e.Source,
|
|
CreatedAt: e.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
|
}
|
|
}
|