Bulk multi-select actions, barcode format, batch printing, site name on login
Multi-select (Ctrl/Cmd+click toggle, Shift+click range, right-click the
active selection for bulk actions - same conventions as a native file
manager):
- Print Labels (N) / Export CSV (N) for any mix of individually-picked
assets and whole folders. Labels/export now take asset_ids/folder_ids
instead of a single asset_id/folder_id.
- Edit Status..., Check Out..., Duplicate, Move to..., and Delete, bulk
versions of the existing single-asset actions. Checkout/Move modals
were generalized to accept an array of ids instead of just one; status
editing got its own lightweight POST /app/assets/{id}/status endpoint
(assets.SetStatus) so a bulk status change doesn't need to resend every
other field the way a full PATCH would.
Labels:
- Choose QR (a /scan/<tag> URL, for a phone camera) or Code128 barcode
(the bare Asset ID, for a USB scanner typing into the search box) via a
format selector on the print page. New barcodeDataURI using
github.com/boombuler/barcode alongside the existing QR generator.
- "Start at #" lets printing resume partway into an already-used sheet
instead of wasting it on a fresh one.
- Added the Avery 5260 label template (same grid as 5160, rounded
corners, so it isn't flagged approximate).
Also: the configured Site Name now shows on the login page too, not just
the authenticated navbar.
This commit is contained in:
+52
-19
@@ -290,13 +290,8 @@ func (s *Store) Export(ctx context.Context, w io.Writer, f ListFilter) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cw := csv.NewWriter(w)
|
||||
header := []string{"Asset ID", "Name", "Item Type", "Folder", "Status", "Assigned User", "Notes", "Quantity", "Created At", "Updated At"}
|
||||
for _, k := range fieldDefs {
|
||||
header = append(header, cfColPrefix+k)
|
||||
}
|
||||
if err := cw.Write(header); err != nil {
|
||||
if err := writeAssetHeader(cw, fieldDefs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -312,19 +307,7 @@ func (s *Store) Export(ctx context.Context, w io.Writer, f ListFilter) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
row := []string{
|
||||
a.AssetTag, a.Name, a.ItemTypeName, folderPathString(a.FolderPath), a.StatusName, a.AssignedUsername, a.Notes,
|
||||
strconv.Itoa(a.Quantity),
|
||||
a.CreatedAt.Format("2006-01-02 15:04:05"), a.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
values := map[string]string{}
|
||||
for _, fv := range a.CustomFields {
|
||||
values[fv.Def.FieldKey] = fv.Value
|
||||
}
|
||||
for _, k := range fieldDefs {
|
||||
row = append(row, values[k])
|
||||
}
|
||||
if err := cw.Write(row); err != nil {
|
||||
if err := writeAssetRow(cw, a, fieldDefs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -337,6 +320,56 @@ func (s *Store) Export(ctx context.Context, w io.Writer, f ListFilter) error {
|
||||
return cw.Error()
|
||||
}
|
||||
|
||||
// ExportByIDs writes exactly the given assets (in the given order) as CSV -
|
||||
// used for a multi-select "Export CSV" batch that doesn't map onto a single
|
||||
// ListFilter (an arbitrary mix of individually-picked assets and whole
|
||||
// folders).
|
||||
func (s *Store) ExportByIDs(ctx context.Context, w io.Writer, ids []int64) error {
|
||||
fieldDefs, err := s.allFieldKeys(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cw := csv.NewWriter(w)
|
||||
if err := writeAssetHeader(cw, fieldDefs); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range ids {
|
||||
a, err := s.Get(ctx, id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err := writeAssetRow(cw, a, fieldDefs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cw.Flush()
|
||||
return cw.Error()
|
||||
}
|
||||
|
||||
func writeAssetHeader(cw *csv.Writer, fieldDefs []string) error {
|
||||
header := []string{"Asset ID", "Name", "Item Type", "Folder", "Status", "Assigned User", "Notes", "Quantity", "Created At", "Updated At"}
|
||||
for _, k := range fieldDefs {
|
||||
header = append(header, cfColPrefix+k)
|
||||
}
|
||||
return cw.Write(header)
|
||||
}
|
||||
|
||||
func writeAssetRow(cw *csv.Writer, a *models.Asset, fieldDefs []string) error {
|
||||
row := []string{
|
||||
a.AssetTag, a.Name, a.ItemTypeName, folderPathString(a.FolderPath), a.StatusName, a.AssignedUsername, a.Notes,
|
||||
strconv.Itoa(a.Quantity),
|
||||
a.CreatedAt.Format("2006-01-02 15:04:05"), a.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
values := map[string]string{}
|
||||
for _, fv := range a.CustomFields {
|
||||
values[fv.Def.FieldKey] = fv.Value
|
||||
}
|
||||
for _, k := range fieldDefs {
|
||||
row = append(row, values[k])
|
||||
}
|
||||
return cw.Write(row)
|
||||
}
|
||||
|
||||
func folderPathString(path []models.BreadcrumbItem) string {
|
||||
names := make([]string, len(path))
|
||||
for i, p := range path {
|
||||
|
||||
Reference in New Issue
Block a user