diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 6525c53a..1cbb82bf 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -3483,6 +3483,19 @@ const docTemplate = `{ "id": { "type": "string" }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-nullable": true, + "x-omitempty": true + }, + "locationId": { + "type": "string", + "x-nullable": true, + "x-omitempty": true + }, "quantity": { "type": "integer", "x-nullable": true, diff --git a/backend/app/api/static/docs/openapi-3.json b/backend/app/api/static/docs/openapi-3.json index b0bcc2d8..2ae3681d 100644 --- a/backend/app/api/static/docs/openapi-3.json +++ b/backend/app/api/static/docs/openapi-3.json @@ -3661,6 +3661,19 @@ "id": { "type": "string" }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-omitempty": true, + "nullable": true + }, + "locationId": { + "type": "string", + "x-omitempty": true, + "nullable": true + }, "quantity": { "type": "integer", "x-omitempty": true, diff --git a/backend/app/api/static/docs/openapi-3.yaml b/backend/app/api/static/docs/openapi-3.yaml index 9224a088..bc56d4a2 100644 --- a/backend/app/api/static/docs/openapi-3.yaml +++ b/backend/app/api/static/docs/openapi-3.yaml @@ -2293,6 +2293,16 @@ components: properties: id: type: string + labelIds: + type: array + items: + type: string + x-omitempty: true + nullable: true + locationId: + type: string + x-omitempty: true + nullable: true quantity: type: integer x-omitempty: true diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 5c5e7aa2..3fd96322 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -3481,6 +3481,19 @@ "id": { "type": "string" }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-nullable": true, + "x-omitempty": true + }, + "locationId": { + "type": "string", + "x-nullable": true, + "x-omitempty": true + }, "quantity": { "type": "integer", "x-nullable": true, diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index ac352ed2..1fc5f05e 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -875,6 +875,16 @@ definitions: properties: id: type: string + labelIds: + items: + type: string + type: array + x-nullable: true + x-omitempty: true + locationId: + type: string + x-nullable: true + x-omitempty: true quantity: type: integer x-nullable: true diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index 4fb50255..455fb21f 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -120,9 +120,11 @@ type ( } ItemPatch struct { - ID uuid.UUID `json:"id"` - Quantity *int `json:"quantity,omitempty" extensions:"x-nullable,x-omitempty"` - ImportRef *string `json:"-,omitempty" extensions:"x-nullable,x-omitempty"` + ID uuid.UUID `json:"id"` + Quantity *int `json:"quantity,omitempty" extensions:"x-nullable,x-omitempty"` + ImportRef *string `json:"-,omitempty" extensions:"x-nullable,x-omitempty"` + LocationID uuid.UUID `json:"locationId" extensions:"x-nullable,x-omitempty"` + LabelIDs []uuid.UUID `json:"labelIds" extensions:"x-nullable,x-omitempty"` } ItemSummary struct { @@ -814,7 +816,20 @@ func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, gid uuid.UUID } func (e *ItemsRepository) Patch(ctx context.Context, gid, id uuid.UUID, data ItemPatch) error { - q := e.db.Item.Update(). + tx, err := e.db.Tx(ctx) + if err != nil { + return err + } + committed := false + defer func() { + if !committed { + if err := tx.Rollback(); err != nil { + log.Warn().Err(err).Msg("failed to rollback transaction during item patch") + } + } + }() + + q := tx.Item.Update(). Where( item.ID(id), item.HasGroupWith(group.ID(gid)), @@ -828,8 +843,81 @@ func (e *ItemsRepository) Patch(ctx context.Context, gid, id uuid.UUID, data Ite q.SetQuantity(*data.Quantity) } + if data.LocationID != uuid.Nil { + q.SetLocationID(data.LocationID) + } + + err = q.Exec(ctx) + if err != nil { + return err + } + + if data.LabelIDs != nil { + currentLabels, err := tx.Item.Query().Where(item.ID(id), item.HasGroupWith(group.ID(gid))).QueryLabel().All(ctx) + if err != nil { + return err + } + set := newIDSet(currentLabels) + + addLabels := []uuid.UUID{} + for _, l := range data.LabelIDs { + if set.Contains(l) { + set.Remove(l) + } else { + addLabels = append(addLabels, l) + } + } + + if len(addLabels) > 0 { + if err := tx.Item.Update(). + Where(item.ID(id), item.HasGroupWith(group.ID(gid))). + AddLabelIDs(addLabels...). + Exec(ctx); err != nil { + return err + } + } + if set.Len() > 0 { + if err := tx.Item.Update(). + Where(item.ID(id), item.HasGroupWith(group.ID(gid))). + RemoveLabelIDs(set.Slice()...). + Exec(ctx); err != nil { + return err + } + } + } + + if data.LocationID != uuid.Nil { + itemEnt, err := tx.Item.Query().Where(item.ID(id), item.HasGroupWith(group.ID(gid))).Only(ctx) + if err != nil { + return err + } + if itemEnt.SyncChildItemsLocations { + children, err := tx.Item.Query().Where(item.ID(id), item.HasGroupWith(group.ID(gid))).QueryChildren().All(ctx) + if err != nil { + return err + } + for _, child := range children { + childLocation, err := child.QueryLocation().First(ctx) + if err != nil { + return err + } + if data.LocationID != childLocation.ID { + err = child.Update().SetLocationID(data.LocationID).Exec(ctx) + if err != nil { + return err + } + } + } + } + } + + if err := tx.Commit(); err != nil { + return err + } + committed = true + e.publishMutationEvent(gid) - return q.Exec(ctx) + return nil } func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, gid uuid.UUID, name string) ([]string, error) { diff --git a/docs/en/api/openapi-3.0.json b/docs/en/api/openapi-3.0.json index b0bcc2d8..2ae3681d 100644 --- a/docs/en/api/openapi-3.0.json +++ b/docs/en/api/openapi-3.0.json @@ -3661,6 +3661,19 @@ "id": { "type": "string" }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-omitempty": true, + "nullable": true + }, + "locationId": { + "type": "string", + "x-omitempty": true, + "nullable": true + }, "quantity": { "type": "integer", "x-omitempty": true, diff --git a/docs/en/api/openapi-3.0.yaml b/docs/en/api/openapi-3.0.yaml index 9224a088..bc56d4a2 100644 --- a/docs/en/api/openapi-3.0.yaml +++ b/docs/en/api/openapi-3.0.yaml @@ -2293,6 +2293,16 @@ components: properties: id: type: string + labelIds: + type: array + items: + type: string + x-omitempty: true + nullable: true + locationId: + type: string + x-omitempty: true + nullable: true quantity: type: integer x-omitempty: true diff --git a/docs/en/api/swagger-2.0.json b/docs/en/api/swagger-2.0.json index 5c5e7aa2..3fd96322 100644 --- a/docs/en/api/swagger-2.0.json +++ b/docs/en/api/swagger-2.0.json @@ -3481,6 +3481,19 @@ "id": { "type": "string" }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-nullable": true, + "x-omitempty": true + }, + "locationId": { + "type": "string", + "x-nullable": true, + "x-omitempty": true + }, "quantity": { "type": "integer", "x-nullable": true, diff --git a/docs/en/api/swagger-2.0.yaml b/docs/en/api/swagger-2.0.yaml index ac352ed2..1fc5f05e 100644 --- a/docs/en/api/swagger-2.0.yaml +++ b/docs/en/api/swagger-2.0.yaml @@ -875,6 +875,16 @@ definitions: properties: id: type: string + labelIds: + items: + type: string + type: array + x-nullable: true + x-omitempty: true + locationId: + type: string + x-nullable: true + x-omitempty: true quantity: type: integer x-nullable: true diff --git a/docs/en/user-guide/tips-tricks.md b/docs/en/user-guide/tips-tricks.md index 25265dbf..8b28839c 100644 --- a/docs/en/user-guide/tips-tricks.md +++ b/docs/en/user-guide/tips-tricks.md @@ -102,3 +102,11 @@ The copy to clipboard functionality requires a secure context (HTTPS or localhos To enable this feature: - Use HTTPS by setting up a reverse proxy (like Nginx or Caddy) - OR access Homebox through localhost + +## Open Multiple Items in New Tabs + +By default browsers prevent opening multiple tabs with one click, to allow for the `View Items` button to work you therefore need to enable a setting usually called `Allow pop-ups and redirects` or similar for the domain you're using Homebox on. + +- Chrome: [Block or allow pop-ups in Chrome](https://support.google.com/chrome/answer/95472?hl=en-GB&co=GENIE.Platform%3DDesktop#zippy=%2Callow-pop-ups-and-redirects-from-a-site) +- Firefox: [Pop-up blocker settings, exceptions and troubleshooting](https://support.mozilla.org/en-US/kb/pop-blocker-settings-exceptions-troubleshooting) +- Safari: [Block pop-up ads and windows in Safari](https://support.apple.com/en-gb/102524) diff --git a/frontend/components/Base/SectionHeader.vue b/frontend/components/Base/SectionHeader.vue index 10d9e144..fccdca8a 100644 --- a/frontend/components/Base/SectionHeader.vue +++ b/frontend/components/Base/SectionHeader.vue @@ -3,6 +3,7 @@ + diff --git a/frontend/components/Item/BarcodeModal.vue b/frontend/components/Item/BarcodeModal.vue index dc00febf..a489ca34 100644 --- a/frontend/components/Item/BarcodeModal.vue +++ b/frontend/components/Item/BarcodeModal.vue @@ -115,7 +115,6 @@ import MdiAlertCircleOutline from "~icons/mdi/alert-circle-outline"; import MdiBarcode from "~icons/mdi/barcode"; import MdiLoading from "~icons/mdi/loading"; - import type { TableData } from "~/components/Item/View/Table.types"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Separator } from "@/components/ui/separator"; @@ -225,7 +224,8 @@ } } - function extractValue(data: TableData, value: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function extractValue(data: Record, value: string) { const parts = value.split("."); let current = data; for (const part of parts) { diff --git a/frontend/components/Item/Card.vue b/frontend/components/Item/Card.vue index b95be4b0..5becbecd 100644 --- a/frontend/components/Item/Card.vue +++ b/frontend/components/Item/Card.vue @@ -1,5 +1,13 @@