forked from mirrors/homebox
feat: asset tags/ids (#142)
* add schema * run db migration * bulk seed asset IDs * breaking: update runtime options * conditionally increment asset IDs * update API endpoints * fix import asset id assignment * refactor display + marshal/unmarshal * add docs page * add to form field * hide 000-000 values * update ENV vars
This commit is contained in:
parent
976f68252d
commit
6dc2ae1bea
32 changed files with 905 additions and 72 deletions
30
backend/internal/data/repo/asset_id_type.go
Normal file
30
backend/internal/data/repo/asset_id_type.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type AssetID int
|
||||
|
||||
func (aid AssetID) MarshalJSON() ([]byte, error) {
|
||||
aidStr := fmt.Sprintf("%06d", aid)
|
||||
aidStr = fmt.Sprintf("%s-%s", aidStr[:3], aidStr[3:])
|
||||
return []byte(fmt.Sprintf(`"%s"`, aidStr)), nil
|
||||
|
||||
}
|
||||
|
||||
func (aid *AssetID) UnmarshalJSON(d []byte) error {
|
||||
d = bytes.Replace(d, []byte(`"`), []byte(``), -1)
|
||||
d = bytes.Replace(d, []byte(`-`), []byte(``), -1)
|
||||
|
||||
aidInt, err := strconv.Atoi(string(d))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*aid = AssetID(aidInt)
|
||||
return nil
|
||||
|
||||
}
|
115
backend/internal/data/repo/asset_id_type_test.go
Normal file
115
backend/internal/data/repo/asset_id_type_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAssetID_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
aid AssetID
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic test",
|
||||
aid: 123,
|
||||
want: []byte(`"000-123"`),
|
||||
},
|
||||
{
|
||||
name: "zero test",
|
||||
aid: 0,
|
||||
want: []byte(`"000-000"`),
|
||||
},
|
||||
{
|
||||
name: "large int",
|
||||
aid: 123456789,
|
||||
want: []byte(`"123-456789"`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.aid.MarshalJSON()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AssetID.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("AssetID.MarshalJSON() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetID_UnmarshalJSON(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
aid *AssetID
|
||||
args args
|
||||
want AssetID
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic test",
|
||||
aid: new(AssetID),
|
||||
want: 123,
|
||||
args: args{
|
||||
data: []byte(`{"AssetID":"000123"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dashed format",
|
||||
aid: new(AssetID),
|
||||
want: 123,
|
||||
args: args{
|
||||
data: []byte(`{"AssetID":"000-123"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no leading zeros",
|
||||
aid: new(AssetID),
|
||||
want: 123,
|
||||
args: args{
|
||||
data: []byte(`{"AssetID":"123"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trailing zeros",
|
||||
aid: new(AssetID),
|
||||
want: 123000,
|
||||
args: args{
|
||||
data: []byte(`{"AssetID":"000123000"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "large int",
|
||||
aid: new(AssetID),
|
||||
want: 123456789,
|
||||
args: args{
|
||||
data: []byte(`{"AssetID":"123456789"}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st := struct {
|
||||
AssetID AssetID `json:"AssetID"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(tt.args.data, &st)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AssetID.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if st.AssetID != tt.want {
|
||||
t.Errorf("AssetID.UnmarshalJSON() = %v, want %v", st.AssetID, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ type (
|
|||
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AssetID AssetID `json:"-"`
|
||||
|
||||
// Edges
|
||||
LocationID uuid.UUID `json:"locationId"`
|
||||
|
@ -52,6 +53,7 @@ type (
|
|||
ItemUpdate struct {
|
||||
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
AssetID AssetID `json:"assetId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Quantity int `json:"quantity"`
|
||||
|
@ -107,6 +109,7 @@ type (
|
|||
ItemOut struct {
|
||||
Parent *ItemSummary `json:"parent,omitempty" extensions:"x-nullable,x-omitempty"`
|
||||
ItemSummary
|
||||
AssetID AssetID `json:"assetId,string"`
|
||||
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
ModelNumber string `json:"modelNumber"`
|
||||
|
@ -215,6 +218,7 @@ func mapItemOut(item *ent.Item) ItemOut {
|
|||
|
||||
return ItemOut{
|
||||
Parent: parent,
|
||||
AssetID: AssetID(item.AssetID),
|
||||
ItemSummary: mapItemSummary(item),
|
||||
LifetimeWarranty: item.LifetimeWarranty,
|
||||
WarrantyExpires: item.WarrantyExpires,
|
||||
|
@ -359,13 +363,53 @@ func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSumm
|
|||
All(ctx))
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID) ([]ItemSummary, error) {
|
||||
q := e.db.Item.Query().Where(
|
||||
item.HasGroupWith(group.ID(GID)),
|
||||
item.AssetID(0),
|
||||
).Order(
|
||||
ent.Asc(item.FieldCreatedAt),
|
||||
)
|
||||
|
||||
return mapItemsSummaryErr(q.All(ctx))
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID) (AssetID, error) {
|
||||
q := e.db.Item.Query().Where(
|
||||
item.HasGroupWith(group.ID(GID)),
|
||||
).Order(
|
||||
ent.Desc(item.FieldAssetID),
|
||||
).Limit(1)
|
||||
|
||||
result, err := q.First(ctx)
|
||||
if err != nil {
|
||||
if ent.IsNotFound(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return AssetID(result.AssetID), nil
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) SetAssetID(ctx context.Context, GID uuid.UUID, ID uuid.UUID, assetID AssetID) error {
|
||||
q := e.db.Item.Update().Where(
|
||||
item.HasGroupWith(group.ID(GID)),
|
||||
item.ID(ID),
|
||||
)
|
||||
|
||||
_, err := q.SetAssetID(int(assetID)).Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCreate) (ItemOut, error) {
|
||||
q := e.db.Item.Create().
|
||||
SetImportRef(data.ImportRef).
|
||||
SetName(data.Name).
|
||||
SetDescription(data.Description).
|
||||
SetGroupID(gid).
|
||||
SetLocationID(data.LocationID)
|
||||
SetLocationID(data.LocationID).
|
||||
SetAssetID(int(data.AssetID))
|
||||
|
||||
if data.LabelIDs != nil && len(data.LabelIDs) > 0 {
|
||||
q.AddLabelIDs(data.LabelIDs...)
|
||||
|
@ -414,7 +458,8 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data
|
|||
SetInsured(data.Insured).
|
||||
SetWarrantyExpires(data.WarrantyExpires).
|
||||
SetWarrantyDetails(data.WarrantyDetails).
|
||||
SetQuantity(data.Quantity)
|
||||
SetQuantity(data.Quantity).
|
||||
SetAssetID(int(data.AssetID))
|
||||
|
||||
currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue