diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index 272011f8..2619f4d1 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -323,17 +323,17 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) - csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GID) + csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID) if err != nil { log.Err(err).Msg("failed to export items") return validate.NewRequestError(err, http.StatusInternalServerError) } - w.Header().Set("Content-Type", "text/tsv") - w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.tsv") + w.Header().Set("Content-Type", "text/csv") + w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.csv") writer := csv.NewWriter(w) - writer.Comma = '\t' + writer.Comma = ',' return writer.WriteAll(csvData) } } diff --git a/backend/app/api/handlers/v1/v1_ctrl_reporting.go b/backend/app/api/handlers/v1/v1_ctrl_reporting.go index e51465b3..d3b712f8 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_reporting.go +++ b/backend/app/api/handlers/v1/v1_ctrl_reporting.go @@ -1,9 +1,9 @@ package v1 import ( - "net/http" "github.com/hay-kot/httpkit/errchain" "github.com/sysadminsmedia/homebox/backend/internal/core/services" + "net/http" ) // HandleBillOfMaterialsExport godoc @@ -18,7 +18,7 @@ func (ctrl *V1Controller) HandleBillOfMaterialsExport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { actor := services.UseUserCtx(r.Context()) - csv, err := ctrl.svc.Items.ExportBillOfMaterialsTSV(r.Context(), actor.GroupID) + csv, err := ctrl.svc.Items.ExportBillOfMaterialsCSV(r.Context(), actor.GroupID) if err != nil { return err } diff --git a/backend/go.sum b/backend/go.sum index 42650d5e..b7c00b66 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -111,6 +111,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -120,6 +122,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E= github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= @@ -137,6 +141,10 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/backend/internal/core/services/reporting/bill_of_materials.go b/backend/internal/core/services/reporting/bill_of_materials.go index 7bdf2143..e6873d70 100644 --- a/backend/internal/core/services/reporting/bill_of_materials.go +++ b/backend/internal/core/services/reporting/bill_of_materials.go @@ -20,9 +20,9 @@ type BillOfMaterialsEntry struct { TotalPrice float64 `csv:"Total Price"` } -// BillOfMaterialsTSV returns a byte slice of the Bill of Materials for a given GID in TSV format +// BillOfMaterialsCSV returns a byte slice of the Bill of Materials for a given GID in CSV format // See BillOfMaterialsEntry for the format of the output -func BillOfMaterialsTSV(entities []repo.ItemOut) ([]byte, error) { +func BillOfMaterialsCSV(entities []repo.ItemOut) ([]byte, error) { bomEntries := make([]BillOfMaterialsEntry, len(entities)) for i, entity := range entities { bomEntries[i] = BillOfMaterialsEntry{ diff --git a/backend/internal/core/services/reporting/io_row.go b/backend/internal/core/services/reporting/io_row.go index e3bc0b1b..ca3c8010 100644 --- a/backend/internal/core/services/reporting/io_row.go +++ b/backend/internal/core/services/reporting/io_row.go @@ -12,7 +12,7 @@ type ExportItemFields struct { Value string } -type ExportTSVRow struct { +type ExportCSVRow struct { ImportRef string `csv:"HB.import_ref"` Location LocationString `csv:"HB.location"` LabelStr LabelString `csv:"HB.labels"` diff --git a/backend/internal/core/services/reporting/io_sheet.go b/backend/internal/core/services/reporting/io_sheet.go index c6c52872..08676bd0 100644 --- a/backend/internal/core/services/reporting/io_sheet.go +++ b/backend/internal/core/services/reporting/io_sheet.go @@ -19,12 +19,12 @@ import ( // items from homebox. It is used to read/write the data from/to a CSV/TSV file given // the standard format of the file. // -// See ExportTSVRow for the format of the data in the sheet. +// See ExportCSVRow for the format of the data in the sheet. type IOSheet struct { headers []string custom []int index map[string]int - Rows []ExportTSVRow + Rows []ExportCSVRow } func (s *IOSheet) indexHeaders() { @@ -70,16 +70,16 @@ func (s *IOSheet) Read(data io.Reader) error { } s.headers = sheet[0] - s.Rows = make([]ExportTSVRow, len(sheet)-1) + s.Rows = make([]ExportCSVRow, len(sheet)-1) for i, row := range sheet[1:] { if len(row) != len(s.headers) { return fmt.Errorf("row has %d columns, expected %d", len(row), len(s.headers)) } - rowData := ExportTSVRow{} + rowData := ExportCSVRow{} - st := reflect.TypeOf(ExportTSVRow{}) + st := reflect.TypeOf(ExportCSVRow{}) for i := 0; i < st.NumField(); i++ { field := st.Field(i) @@ -154,7 +154,7 @@ func (s *IOSheet) Read(data io.Reader) error { // ReadItems writes the sheet to a writer. func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error { - s.Rows = make([]ExportTSVRow, len(items)) + s.Rows = make([]ExportCSVRow, len(items)) extraHeaders := map[string]struct{}{} @@ -189,7 +189,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. } } - s.Rows[i] = ExportTSVRow{ + s.Rows[i] = ExportCSVRow{ // fill struct Location: locString, LabelStr: labelString, @@ -232,7 +232,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. sort.Strings(customHeaders) - st := reflect.TypeOf(ExportTSVRow{}) + st := reflect.TypeOf(ExportCSVRow{}) // Write headers for i := 0; i < st.NumField(); i++ { @@ -252,8 +252,8 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. return nil } -// TSV writes the current sheet to a writer in TSV format. -func (s *IOSheet) TSV() ([][]string, error) { +// CSV writes the current sheet to a 2d array, for compatibility with TSV/CSV files. +func (s *IOSheet) CSV() ([][]string, error) { memcsv := make([][]string, len(s.Rows)+1) memcsv[0] = s.headers diff --git a/backend/internal/core/services/reporting/io_sheet_test.go b/backend/internal/core/services/reporting/io_sheet_test.go index 5bdbc898..a489d2f8 100644 --- a/backend/internal/core/services/reporting/io_sheet_test.go +++ b/backend/internal/core/services/reporting/io_sheet_test.go @@ -27,13 +27,13 @@ func TestSheet_Read(t *testing.T) { tests := []struct { name string data []byte - want []ExportTSVRow + want []ExportCSVRow wantErr bool }{ { name: "minimal import", data: minimalImportCSV, - want: []ExportTSVRow{ + want: []ExportCSVRow{ {Location: LocationString{"loc"}, Name: "Item 1", Quantity: 1, Description: "Description 1"}, {Location: LocationString{"loc"}, Name: "Item 2", Quantity: 2, Description: "Description 2"}, {Location: LocationString{"loc"}, Name: "Item 3", Quantity: 3, Description: "Description 3"}, @@ -42,7 +42,7 @@ func TestSheet_Read(t *testing.T) { { name: "custom field import", data: customFieldImportCSV, - want: []ExportTSVRow{ + want: []ExportCSVRow{ { Location: LocationString{"loc"}, Name: "Item 1", Quantity: 1, Description: "Description 1", Fields: []ExportItemFields{ @@ -72,7 +72,7 @@ func TestSheet_Read(t *testing.T) { { name: "custom types import", data: customTypesImportCSV, - want: []ExportTSVRow{ + want: []ExportCSVRow{ { Name: "Item 1", AssetID: repo.AssetID(1), diff --git a/backend/internal/core/services/service_items.go b/backend/internal/core/services/service_items.go index 55136cef..d6f1896c 100644 --- a/backend/internal/core/services/service_items.go +++ b/backend/internal/core/services/service_items.go @@ -329,7 +329,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re return finished, nil } -func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]string, error) { +func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]string, error) { items, err := svc.repo.Items.GetAll(ctx, GID) if err != nil { return nil, err @@ -342,14 +342,14 @@ func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]strin return nil, err } - return sheet.TSV() + return sheet.CSV() } -func (svc *ItemService) ExportBillOfMaterialsTSV(ctx context.Context, GID uuid.UUID) ([]byte, error) { +func (svc *ItemService) ExportBillOfMaterialsCSV(ctx context.Context, GID uuid.UUID) ([]byte, error) { items, err := svc.repo.Items.GetAll(ctx, GID) if err != nil { return nil, err } - return reporting.BillOfMaterialsTSV(items) + return reporting.BillOfMaterialsCSV(items) } diff --git a/frontend/pages/tools.vue b/frontend/pages/tools.vue index 98bfe302..8dcfebe1 100644 --- a/frontend/pages/tools.vue +++ b/frontend/pages/tools.vue @@ -22,7 +22,7 @@ - Generates a TSV (Tab Separated Values) file that can be imported into a spreadsheet program. This is a + Generates a CSV (Comma Separated Values) file that can be imported into a spreadsheet program. This is a summary of your inventory with basic item and pricing information. @@ -45,7 +45,7 @@ Imports the standard CSV format for Homebox. This will not overwrite any existing items in your inventory. It will only add new items. - + Exports the standard CSV format for Homebox. This will export all items in your inventory. @@ -123,7 +123,7 @@ window.open(url, "_blank"); } - function getExportTSV() { + function getExportCSV() { const url = api.items.exportURL(); window.open(url, "_blank"); }