Implement syncing child's locations to that of parent.

This commit is contained in:
slid1amo2n3e4
2024-10-08 17:14:30 +02:00
committed by Matt Kilgore
parent 7d462a4dd3
commit 17e7e24070
16 changed files with 376 additions and 37 deletions

View File

@@ -2242,6 +2242,9 @@ const docTemplate = `{
"soldTo": {
"type": "string"
},
"syncChildItemsLocations": {
"type": "boolean"
},
"updatedAt": {
"type": "string"
},
@@ -2440,6 +2443,9 @@ const docTemplate = `{
"type": "string",
"maxLength": 255
},
"syncChildItemsLocations": {
"type": "boolean"
},
"warrantyDetails": {
"type": "string"
},

View File

@@ -2235,6 +2235,9 @@
"soldTo": {
"type": "string"
},
"syncChildItemsLocations": {
"type": "boolean"
},
"updatedAt": {
"type": "string"
},
@@ -2433,6 +2436,9 @@
"type": "string",
"maxLength": 255
},
"syncChildItemsLocations": {
"type": "boolean"
},
"warrantyDetails": {
"type": "string"
},

View File

@@ -188,6 +188,8 @@ definitions:
type: string
soldTo:
type: string
syncChildItemsLocations:
type: boolean
updatedAt:
type: string
warrantyDetails:
@@ -323,6 +325,8 @@ definitions:
soldTo:
maxLength: 255
type: string
syncChildItemsLocations:
type: boolean
warrantyDetails:
type: string
warrantyExpires:

View File

@@ -40,6 +40,8 @@ type Item struct {
Archived bool `json:"archived,omitempty"`
// AssetID holds the value of the "asset_id" field.
AssetID int `json:"asset_id,omitempty"`
// SyncChildItemsLocations holds the value of the "sync_child_items_locations" field.
SyncChildItemsLocations bool `json:"sync_child_items_locations,omitempty"`
// SerialNumber holds the value of the "serial_number" field.
SerialNumber string `json:"serial_number,omitempty"`
// ModelNumber holds the value of the "model_number" field.
@@ -181,7 +183,7 @@ func (*Item) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case item.FieldInsured, item.FieldArchived, item.FieldLifetimeWarranty:
case item.FieldInsured, item.FieldArchived, item.FieldSyncChildItemsLocations, item.FieldLifetimeWarranty:
values[i] = new(sql.NullBool)
case item.FieldPurchasePrice, item.FieldSoldPrice:
values[i] = new(sql.NullFloat64)
@@ -280,6 +282,12 @@ func (i *Item) assignValues(columns []string, values []any) error {
} else if value.Valid {
i.AssetID = int(value.Int64)
}
case item.FieldSyncChildItemsLocations:
if value, ok := values[j].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field sync_child_items_locations", values[j])
} else if value.Valid {
i.SyncChildItemsLocations = value.Bool
}
case item.FieldSerialNumber:
if value, ok := values[j].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field serial_number", values[j])
@@ -485,6 +493,9 @@ func (i *Item) String() string {
builder.WriteString("asset_id=")
builder.WriteString(fmt.Sprintf("%v", i.AssetID))
builder.WriteString(", ")
builder.WriteString("sync_child_items_locations=")
builder.WriteString(fmt.Sprintf("%v", i.SyncChildItemsLocations))
builder.WriteString(", ")
builder.WriteString("serial_number=")
builder.WriteString(i.SerialNumber)
builder.WriteString(", ")

View File

@@ -35,6 +35,8 @@ const (
FieldArchived = "archived"
// FieldAssetID holds the string denoting the asset_id field in the database.
FieldAssetID = "asset_id"
// FieldSyncChildItemsLocations holds the string denoting the sync_child_items_locations field in the database.
FieldSyncChildItemsLocations = "sync_child_items_locations"
// FieldSerialNumber holds the string denoting the serial_number field in the database.
FieldSerialNumber = "serial_number"
// FieldModelNumber holds the string denoting the model_number field in the database.
@@ -142,6 +144,7 @@ var Columns = []string{
FieldInsured,
FieldArchived,
FieldAssetID,
FieldSyncChildItemsLocations,
FieldSerialNumber,
FieldModelNumber,
FieldManufacturer,
@@ -209,6 +212,8 @@ var (
DefaultArchived bool
// DefaultAssetID holds the default value on creation for the "asset_id" field.
DefaultAssetID int
// DefaultSyncChildItemsLocations holds the default value on creation for the "sync_child_items_locations" field.
DefaultSyncChildItemsLocations bool
// SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
SerialNumberValidator func(string) error
// ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.
@@ -287,6 +292,11 @@ func ByAssetID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAssetID, opts...).ToFunc()
}
// BySyncChildItemsLocations orders the results by the sync_child_items_locations field.
func BySyncChildItemsLocations(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSyncChildItemsLocations, opts...).ToFunc()
}
// BySerialNumber orders the results by the serial_number field.
func BySerialNumber(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSerialNumber, opts...).ToFunc()

View File

@@ -106,6 +106,11 @@ func AssetID(v int) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldAssetID, v))
}
// SyncChildItemsLocations applies equality check predicate on the "sync_child_items_locations" field. It's identical to SyncChildItemsLocationsEQ.
func SyncChildItemsLocations(v bool) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSyncChildItemsLocations, v))
}
// SerialNumber applies equality check predicate on the "serial_number" field. It's identical to SerialNumberEQ.
func SerialNumber(v string) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSerialNumber, v))
@@ -641,6 +646,16 @@ func AssetIDLTE(v int) predicate.Item {
return predicate.Item(sql.FieldLTE(FieldAssetID, v))
}
// SyncChildItemsLocationsEQ applies the EQ predicate on the "sync_child_items_locations" field.
func SyncChildItemsLocationsEQ(v bool) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSyncChildItemsLocations, v))
}
// SyncChildItemsLocationsNEQ applies the NEQ predicate on the "sync_child_items_locations" field.
func SyncChildItemsLocationsNEQ(v bool) predicate.Item {
return predicate.Item(sql.FieldNEQ(FieldSyncChildItemsLocations, v))
}
// SerialNumberEQ applies the EQ predicate on the "serial_number" field.
func SerialNumberEQ(v string) predicate.Item {
return predicate.Item(sql.FieldEQ(FieldSerialNumber, v))

View File

@@ -185,6 +185,20 @@ func (iu *ItemUpdate) AddAssetID(i int) *ItemUpdate {
return iu
}
// SetSyncChildItemsLocations sets the "sync_child_items_locations" field.
func (iu *ItemUpdate) SetSyncChildItemsLocations(b bool) *ItemUpdate {
iu.mutation.SetSyncChildItemsLocations(b)
return iu
}
// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil.
func (iu *ItemUpdate) SetNillableSyncChildItemsLocations(b *bool) *ItemUpdate {
if b != nil {
iu.SetSyncChildItemsLocations(*b)
}
return iu
}
// SetSerialNumber sets the "serial_number" field.
func (iu *ItemUpdate) SetSerialNumber(s string) *ItemUpdate {
iu.mutation.SetSerialNumber(s)
@@ -836,6 +850,9 @@ func (iu *ItemUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := iu.mutation.AddedAssetID(); ok {
_spec.AddField(item.FieldAssetID, field.TypeInt, value)
}
if value, ok := iu.mutation.SyncChildItemsLocations(); ok {
_spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value)
}
if value, ok := iu.mutation.SerialNumber(); ok {
_spec.SetField(item.FieldSerialNumber, field.TypeString, value)
}
@@ -1393,6 +1410,20 @@ func (iuo *ItemUpdateOne) AddAssetID(i int) *ItemUpdateOne {
return iuo
}
// SetSyncChildItemsLocations sets the "sync_child_items_locations" field.
func (iuo *ItemUpdateOne) SetSyncChildItemsLocations(b bool) *ItemUpdateOne {
iuo.mutation.SetSyncChildItemsLocations(b)
return iuo
}
// SetNillableSyncChildItemsLocations sets the "sync_child_items_locations" field if the given value is not nil.
func (iuo *ItemUpdateOne) SetNillableSyncChildItemsLocations(b *bool) *ItemUpdateOne {
if b != nil {
iuo.SetSyncChildItemsLocations(*b)
}
return iuo
}
// SetSerialNumber sets the "serial_number" field.
func (iuo *ItemUpdateOne) SetSerialNumber(s string) *ItemUpdateOne {
iuo.mutation.SetSerialNumber(s)
@@ -2074,6 +2105,9 @@ func (iuo *ItemUpdateOne) sqlSave(ctx context.Context) (_node *Item, err error)
if value, ok := iuo.mutation.AddedAssetID(); ok {
_spec.AddField(item.FieldAssetID, field.TypeInt, value)
}
if value, ok := iuo.mutation.SyncChildItemsLocations(); ok {
_spec.SetField(item.FieldSyncChildItemsLocations, field.TypeBool, value)
}
if value, ok := iuo.mutation.SerialNumber(); ok {
_spec.SetField(item.FieldSerialNumber, field.TypeString, value)
}

View File

@@ -162,6 +162,7 @@ var (
{Name: "insured", Type: field.TypeBool, Default: false},
{Name: "archived", Type: field.TypeBool, Default: false},
{Name: "asset_id", Type: field.TypeInt, Default: 0},
{Name: "sync_child_items_locations", Type: field.TypeBool, Default: false},
{Name: "serial_number", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "model_number", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "manufacturer", Type: field.TypeString, Nullable: true, Size: 255},
@@ -187,19 +188,19 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "items_groups_items",
Columns: []*schema.Column{ItemsColumns[24]},
Columns: []*schema.Column{ItemsColumns[25]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.Cascade,
},
{
Symbol: "items_items_children",
Columns: []*schema.Column{ItemsColumns[25]},
Columns: []*schema.Column{ItemsColumns[26]},
RefColumns: []*schema.Column{ItemsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "items_locations_items",
Columns: []*schema.Column{ItemsColumns[26]},
Columns: []*schema.Column{ItemsColumns[27]},
RefColumns: []*schema.Column{LocationsColumns[0]},
OnDelete: schema.Cascade,
},
@@ -213,17 +214,17 @@ var (
{
Name: "item_manufacturer",
Unique: false,
Columns: []*schema.Column{ItemsColumns[13]},
Columns: []*schema.Column{ItemsColumns[14]},
},
{
Name: "item_model_number",
Unique: false,
Columns: []*schema.Column{ItemsColumns[12]},
Columns: []*schema.Column{ItemsColumns[13]},
},
{
Name: "item_serial_number",
Unique: false,
Columns: []*schema.Column{ItemsColumns[11]},
Columns: []*schema.Column{ItemsColumns[12]},
},
{
Name: "item_archived",

View File

@@ -4085,6 +4085,7 @@ type ItemMutation struct {
archived *bool
asset_id *int
addasset_id *int
sync_child_items_locations *bool
serial_number *string
model_number *string
manufacturer *string
@@ -4670,6 +4671,42 @@ func (m *ItemMutation) ResetAssetID() {
m.addasset_id = nil
}
// SetSyncChildItemsLocations sets the "sync_child_items_locations" field.
func (m *ItemMutation) SetSyncChildItemsLocations(b bool) {
m.sync_child_items_locations = &b
}
// SyncChildItemsLocations returns the value of the "sync_child_items_locations" field in the mutation.
func (m *ItemMutation) SyncChildItemsLocations() (r bool, exists bool) {
v := m.sync_child_items_locations
if v == nil {
return
}
return *v, true
}
// OldSyncChildItemsLocations returns the old "sync_child_items_locations" field's value of the Item entity.
// If the Item object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *ItemMutation) OldSyncChildItemsLocations(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSyncChildItemsLocations is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSyncChildItemsLocations requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSyncChildItemsLocations: %w", err)
}
return oldValue.SyncChildItemsLocations, nil
}
// ResetSyncChildItemsLocations resets all changes to the "sync_child_items_locations" field.
func (m *ItemMutation) ResetSyncChildItemsLocations() {
m.sync_child_items_locations = nil
}
// SetSerialNumber sets the "serial_number" field.
func (m *ItemMutation) SetSerialNumber(s string) {
m.serial_number = &s
@@ -5729,7 +5766,7 @@ func (m *ItemMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *ItemMutation) Fields() []string {
fields := make([]string, 0, 23)
fields := make([]string, 0, 24)
if m.created_at != nil {
fields = append(fields, item.FieldCreatedAt)
}
@@ -5760,6 +5797,9 @@ func (m *ItemMutation) Fields() []string {
if m.asset_id != nil {
fields = append(fields, item.FieldAssetID)
}
if m.sync_child_items_locations != nil {
fields = append(fields, item.FieldSyncChildItemsLocations)
}
if m.serial_number != nil {
fields = append(fields, item.FieldSerialNumber)
}
@@ -5827,6 +5867,8 @@ func (m *ItemMutation) Field(name string) (ent.Value, bool) {
return m.Archived()
case item.FieldAssetID:
return m.AssetID()
case item.FieldSyncChildItemsLocations:
return m.SyncChildItemsLocations()
case item.FieldSerialNumber:
return m.SerialNumber()
case item.FieldModelNumber:
@@ -5882,6 +5924,8 @@ func (m *ItemMutation) OldField(ctx context.Context, name string) (ent.Value, er
return m.OldArchived(ctx)
case item.FieldAssetID:
return m.OldAssetID(ctx)
case item.FieldSyncChildItemsLocations:
return m.OldSyncChildItemsLocations(ctx)
case item.FieldSerialNumber:
return m.OldSerialNumber(ctx)
case item.FieldModelNumber:
@@ -5987,6 +6031,13 @@ func (m *ItemMutation) SetField(name string, value ent.Value) error {
}
m.SetAssetID(v)
return nil
case item.FieldSyncChildItemsLocations:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSyncChildItemsLocations(v)
return nil
case item.FieldSerialNumber:
v, ok := value.(string)
if !ok {
@@ -6289,6 +6340,9 @@ func (m *ItemMutation) ResetField(name string) error {
case item.FieldAssetID:
m.ResetAssetID()
return nil
case item.FieldSyncChildItemsLocations:
m.ResetSyncChildItemsLocations()
return nil
case item.FieldSerialNumber:
m.ResetSerialNumber()
return nil

View File

@@ -259,36 +259,40 @@ func init() {
itemDescAssetID := itemFields[5].Descriptor()
// item.DefaultAssetID holds the default value on creation for the asset_id field.
item.DefaultAssetID = itemDescAssetID.Default.(int)
// itemDescSyncChildItemsLocations is the schema descriptor for sync_child_items_locations field.
itemDescSyncChildItemsLocations := itemFields[6].Descriptor()
// item.DefaultSyncChildItemsLocations holds the default value on creation for the sync_child_items_locations field.
item.DefaultSyncChildItemsLocations = itemDescSyncChildItemsLocations.Default.(bool)
// itemDescSerialNumber is the schema descriptor for serial_number field.
itemDescSerialNumber := itemFields[6].Descriptor()
itemDescSerialNumber := itemFields[7].Descriptor()
// item.SerialNumberValidator is a validator for the "serial_number" field. It is called by the builders before save.
item.SerialNumberValidator = itemDescSerialNumber.Validators[0].(func(string) error)
// itemDescModelNumber is the schema descriptor for model_number field.
itemDescModelNumber := itemFields[7].Descriptor()
itemDescModelNumber := itemFields[8].Descriptor()
// item.ModelNumberValidator is a validator for the "model_number" field. It is called by the builders before save.
item.ModelNumberValidator = itemDescModelNumber.Validators[0].(func(string) error)
// itemDescManufacturer is the schema descriptor for manufacturer field.
itemDescManufacturer := itemFields[8].Descriptor()
itemDescManufacturer := itemFields[9].Descriptor()
// item.ManufacturerValidator is a validator for the "manufacturer" field. It is called by the builders before save.
item.ManufacturerValidator = itemDescManufacturer.Validators[0].(func(string) error)
// itemDescLifetimeWarranty is the schema descriptor for lifetime_warranty field.
itemDescLifetimeWarranty := itemFields[9].Descriptor()
itemDescLifetimeWarranty := itemFields[10].Descriptor()
// item.DefaultLifetimeWarranty holds the default value on creation for the lifetime_warranty field.
item.DefaultLifetimeWarranty = itemDescLifetimeWarranty.Default.(bool)
// itemDescWarrantyDetails is the schema descriptor for warranty_details field.
itemDescWarrantyDetails := itemFields[11].Descriptor()
itemDescWarrantyDetails := itemFields[12].Descriptor()
// item.WarrantyDetailsValidator is a validator for the "warranty_details" field. It is called by the builders before save.
item.WarrantyDetailsValidator = itemDescWarrantyDetails.Validators[0].(func(string) error)
// itemDescPurchasePrice is the schema descriptor for purchase_price field.
itemDescPurchasePrice := itemFields[14].Descriptor()
itemDescPurchasePrice := itemFields[15].Descriptor()
// item.DefaultPurchasePrice holds the default value on creation for the purchase_price field.
item.DefaultPurchasePrice = itemDescPurchasePrice.Default.(float64)
// itemDescSoldPrice is the schema descriptor for sold_price field.
itemDescSoldPrice := itemFields[17].Descriptor()
itemDescSoldPrice := itemFields[18].Descriptor()
// item.DefaultSoldPrice holds the default value on creation for the sold_price field.
item.DefaultSoldPrice = itemDescSoldPrice.Default.(float64)
// itemDescSoldNotes is the schema descriptor for sold_notes field.
itemDescSoldNotes := itemFields[18].Descriptor()
itemDescSoldNotes := itemFields[19].Descriptor()
// item.SoldNotesValidator is a validator for the "sold_notes" field. It is called by the builders before save.
item.SoldNotesValidator = itemDescSoldNotes.Validators[0].(func(string) error)
// itemDescID is the schema descriptor for id field.

View File

@@ -51,6 +51,8 @@ func (Item) Fields() []ent.Field {
Default(false),
field.Int("asset_id").
Default(0),
field.Bool("sync_child_items_locations").
Default(false),
// ------------------------------------
// item identification

View File

@@ -55,11 +55,11 @@ type (
}
ItemCreate struct {
ImportRef string `json:"-"`
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
AssetID AssetID `json:"-"`
ImportRef string `json:"-"`
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
AssetID AssetID `json:"-"`
// Edges
LocationID uuid.UUID `json:"locationId"`
@@ -67,14 +67,15 @@ type (
}
ItemUpdate struct {
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"`
ID uuid.UUID `json:"id"`
AssetID AssetID `json:"assetId" swaggertype:"string"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
Quantity int `json:"quantity"`
Insured bool `json:"insured"`
Archived bool `json:"archived"`
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"`
ID uuid.UUID `json:"id"`
AssetID AssetID `json:"assetId" swaggertype:"string"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
Quantity int `json:"quantity"`
Insured bool `json:"insured"`
Archived bool `json:"archived"`
SyncChildItemsLocations bool `json:"syncChildItemsLocations"`
// Edges
LocationID uuid.UUID `json:"locationId"`
@@ -137,6 +138,8 @@ type (
ItemSummary
AssetID AssetID `json:"assetId,string"`
SyncChildItemsLocations bool `json:"syncChildItemsLocations"`
SerialNumber string `json:"serialNumber"`
ModelNumber string `json:"modelNumber"`
Manufacturer string `json:"manufacturer"`
@@ -248,12 +251,13 @@ func mapItemOut(item *ent.Item) ItemOut {
}
return ItemOut{
Parent: parent,
AssetID: AssetID(item.AssetID),
ItemSummary: mapItemSummary(item),
LifetimeWarranty: item.LifetimeWarranty,
WarrantyExpires: types.DateFromTime(item.WarrantyExpires),
WarrantyDetails: item.WarrantyDetails,
Parent: parent,
AssetID: AssetID(item.AssetID),
ItemSummary: mapItemSummary(item),
LifetimeWarranty: item.LifetimeWarranty,
WarrantyExpires: types.DateFromTime(item.WarrantyExpires),
WarrantyDetails: item.WarrantyDetails,
SyncChildItemsLocations: item.SyncChildItemsLocations,
// Identification
SerialNumber: item.SerialNumber,
@@ -606,7 +610,8 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data
SetWarrantyExpires(data.WarrantyExpires.Time()).
SetWarrantyDetails(data.WarrantyDetails).
SetQuantity(data.Quantity).
SetAssetID(int(data.AssetID))
SetAssetID(int(data.AssetID)).
SetSyncChildItemsLocations(data.SyncChildItemsLocations)
currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx)
if err != nil {
@@ -633,6 +638,28 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data
q.ClearParent()
}
if data.SyncChildItemsLocations {
children, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryChildren().All(ctx)
if err != nil {
return ItemOut{}, err
}
location := data.LocationID
for _, child := range children {
child_location, err := child.QueryLocation().First(ctx)
if err != nil {
return ItemOut{}, err
}
if location != child_location.ID {
err = child.Update().SetLocationID(location).Exec(ctx)
if err != nil {
return ItemOut{}, err
}
}
}
}
err = q.Exec(ctx)
if err != nil {
return ItemOut{}, err

View File

@@ -0,0 +1,36 @@
<template>
<div v-if="!inline" class="form-control w-full">
<label class="label cursor-pointer">
<input v-model="value" type="checkbox" class="toggle toggle-primary" />
<span class="label-text"> {{ label }}</span>
</label>
</div>
<div v-else class="label cursor-pointer sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label>
<span class="label-text">
{{ label }}
</span>
</label>
<input v-model="value" type="checkbox" class="toggle toggle-primary" />
</div>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
inline: {
type: Boolean,
default: false,
},
label: {
type: String,
default: "",
},
});
const value = useVModel(props, "modelValue");
</script>

View File

@@ -193,4 +193,72 @@ describe("user should be able to create an item and add an attachment", () => {
cleanup();
});
test("child items sync their location to their parent", async () => {
const api = await sharedUserClient();
const [parentLocation, parentCleanup] = await useLocation(api);
const [childsLocation, childsCleanup] = await useLocation(api);
const { response: parentResponse, data: parent } = await api.items.create({
name: "parent-item",
labelIds: [],
description: "test-description",
locationId: parentLocation.id,
});
expect(parentResponse.status).toBe(201);
expect(parent.id).toBeTruthy();
const { response: child1Response, data: child1Item } = await api.items.create({
name: "child1-item",
labelIds: [],
description: "test-description",
locationId: childsLocation.id,
});
expect(child1Response.status).toBe(201);
const child1ItemUpdate = {
parentId: parent.id,
...child1Item,
locationId: child1Item.location?.id,
labelIds: []
};
const { response: child1UpdatedResponse, data: child1UpdatedData } = await api.items.update(child1Item.id, child1ItemUpdate as ItemUpdate);
expect(child1UpdatedResponse.status).toBe(200);
const { response: child2Response, data: child2Item } = await api.items.create({
name: "child2-item",
labelIds: [],
description: "test-description",
locationId: childsLocation.id,
});
expect(child2Response.status).toBe(201);
const child2ItemUpdate = {
parentId: parent.id,
...child2Item,
locationId: child2Item.location?.id,
labelIds: []
}
const { response: child2UpdatedResponse, data: child2UpdatedData } = await api.items.update(child2Item.id, child2ItemUpdate as ItemUpdate);
expect(child2UpdatedResponse.status).toBe(200);
const itemUpdate = {
parentId: null,
...parent,
locationId: parentLocation.id,
labelIds: [],
syncChildItemsLocations: true
};
const { response: updateResponse, data: updateData } = await api.items.update(parent.id, itemUpdate)
expect(updateResponse.status).toBe(200);
const { response: child1FinalResponse, data: child1FinalData } = await api.items.get(child1Item.id);
expect(child1FinalResponse.status).toBe(200);
expect(child1FinalData.location?.id).toBe(parentLocation.id);
const { response: child2FinalResponse, data: child2FinalData } = await api.items.get(child2Item.id);
expect(child2FinalResponse.status).toBe(200);
expect(child2FinalData.location?.id).toBe(parentLocation.id);
parentCleanup();
childsCleanup();
});
});

View File

@@ -116,6 +116,7 @@ export interface ItemOut {
/** Sold */
soldTime: Date | string;
soldTo: string;
syncChildItemsLocations: boolean;
updatedAt: Date | string;
warrantyDetails: string;
warrantyExpires: Date | string;
@@ -190,6 +191,7 @@ export interface ItemUpdate {
soldTime: Date | string;
/** @maxLength 255 */
soldTo: string;
syncChildItemsLocations: boolean;
warrantyDetails: string;
warrantyExpires: Date | string;
}

View File

@@ -427,6 +427,63 @@
}
}
async function maybeSyncWithParentLocation() {
if (parent.value && parent.value.id) {
const { data, error } = await api.items.get(parent.value.id);
if (error) {
toast.error("Something went wrong trying to load parent data");
} else {
if (data.syncChildItemsLocations) {
toast.info("Selected parent syncs its children's locations to its own. The location has been updated.");
item.value.location = data.location
}
}
}
}
async function informAboutDesyncingLocationFromParent() {
if (parent.value && parent.value.id) {
const { data, error } = await api.items.get(parent.value.id);
if (error) {
toast.error("Something went wrong trying to load parent data");
}
if (data.syncChildItemsLocations) {
toast.info("Changing location will de-sync it from the parent's location");
}
}
}
async function syncChildItemsLocations() {
if (!item.value.location?.id) {
toast.error("Failed to save item: no location selected");
return;
}
const payload: ItemUpdate = {
...item.value,
locationId: item.value.location?.id,
labelIds: item.value.labels.map(l => l.id),
parentId: parent.value ? parent.value.id : null,
assetId: item.value.assetId,
};
const { error } = await api.items.update(itemId.value, payload);
if (error) {
toast.error("Failed to save item");
return;
}
if (!item.value.syncChildItemsLocations) {
toast.success("Child items' locations will no longer be synced with this item.")
} else {
toast.success("Child items' locations have been synced with this item");
}
}
onMounted(() => {
window.addEventListener("keydown", keyboardSave);
});
@@ -488,8 +545,9 @@
<div class="mt-2 flex flex-wrap items-center justify-between gap-4"></div>
</template>
<div class="mb-6 grid gap-4 border-t px-5 pt-2 md:grid-cols-2">
<LocationSelector v-model="item.location" />
<LocationSelector v-model="item.location" @update:model-value="informAboutDesyncingLocationFromParent()" />
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
<FormToggle v-model="item.syncChildItemsLocations" label="Sync child items' locations" inline @update:model-value="syncChildItemsLocations()" />
<Autocomplete
v-if="preferences.editorAdvancedView"
v-model="parent"
@@ -498,6 +556,7 @@
item-text="name"
label="Parent Item"
no-results-text="Type to search..."
@update:model-value="maybeSyncWithParentLocation()"
/>
</div>