shopping lists in SLE dialog

This commit is contained in:
vabene1111
2025-11-30 15:32:54 +01:00
parent f4eded5b03
commit 8f5593d5ca
6 changed files with 91 additions and 3 deletions

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2025-11-30 14:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0232_shoppinglist'),
]
operations = [
migrations.AddField(
model_name='food',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
migrations.AddField(
model_name='shoppinglistentry',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
migrations.AddField(
model_name='supermarket',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
]

View File

@@ -666,6 +666,7 @@ class Supermarket(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation')
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
@@ -780,6 +781,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
url = models.CharField(max_length=1024, blank=True, null=True, default='')
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
ignore_shopping = models.BooleanField(default=False) # inherited field
onhand_users = models.ManyToManyField(User, blank=True)
description = models.TextField(default='', blank=True)
@@ -1317,6 +1319,7 @@ class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, Pe
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
shopping_lists = models.ManyToManyField(ShoppingList, blank=True)
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries')
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)

View File

@@ -1414,6 +1414,7 @@ class ShoppingListSerializer(SpacedModelSerializer, WritableNestedModelSerialize
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
food = FoodSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False)
shopping_lists = ShoppingListSerializer(many=True, required=False)
list_recipe_data = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
amount = CustomDecimalField()
created_by = UserSerializer(read_only=True)
@@ -1482,7 +1483,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
class Meta:
model = ShoppingListEntry
fields = (
'id', 'list_recipe', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient',
'id', 'list_recipe', 'shopping_lists', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient',
'list_recipe_data', 'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until', 'mealplan_id'
)
read_only_fields = ('id', 'created_by', 'created_at')

View File

@@ -8,6 +8,9 @@
<v-label>{{ $t('Choose_Category') }}</v-label>
<model-select model="SupermarketCategory" @update:modelValue="categoryUpdate" allow-create></model-select>
<v-label>{{ $t('ShoppingList') }}</v-label>
<model-select model="ShoppingList" @update:modelValue="shoppingListUpdate" mode="tags" allow-create></model-select>
<v-row>
<v-col class="pr-0">
<v-btn height="80px" color="info" density="compact" size="small" block stacked
@@ -76,6 +79,9 @@
<v-list-item-subtitle v-if="isDelayed(e)" class="text-info font-weight-bold">
{{ $t('PostponedUntil') }} {{ DateTime.fromJSDate(e.delayUntil!).toLocaleString(DateTime.DATETIME_SHORT) }}
</v-list-item-subtitle>
<v-list-item-subtitle v-if="e.shoppingLists.length > 0" class="text-info font-weight-bold">
<v-chip v-for="sl in e.shoppingLists" label size="x-small" variant="outlined" :color="sl.color" :key="sl.id">{{sl.name}}</v-chip>
</v-list-item-subtitle>
<v-btn-group divided border>
<v-btn icon="" @click="e.amount = e.amount / 2; updateEntryAmount(e)" v-if="!e.ingredient">
@@ -122,8 +128,8 @@
<script setup lang="ts">
import {computed} from "vue";
import {ApiApi, PatchedShoppingListEntry, ShoppingListEntry, SupermarketCategory} from "@/openapi";
import {computed, ref} from "vue";
import {ApiApi, PatchedShoppingListEntry, ShoppingList, ShoppingListEntry, SupermarketCategory} from "@/openapi";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {IShoppingListFood} from "@/types/Shopping";
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
@@ -139,6 +145,8 @@ const {mobile} = useDisplay()
const showDialog = defineModel<Boolean>()
const shoppingListFood = defineModel<IShoppingListFood>('shoppingListFood', {required: true})
const shoppingListUpdateLoading = ref(false)
/**
* returns a flat list of entries for the given shopping list food
*/
@@ -171,6 +179,26 @@ function categoryUpdate(category: SupermarketCategory) {
})
}
/**
* change the shopping list for all entries
* @param shoppingLists
*/
function shoppingListUpdate(shoppingLists: ShoppingList[]) {
const api = new ApiApi()
const promises: Promise<any>[] = []
shoppingListUpdateLoading.value = true
shoppingListFood.value.entries.forEach(e => {
e.shoppingLists = shoppingLists
promises.push(api.apiShoppingListEntryUpdate({id: e.id, shoppingListEntry: e}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}))
})
Promise.all(promises).finally(() => shoppingListUpdateLoading.value = false)
}
/**
* add new entry for currently selected food type
*/

View File

@@ -19,6 +19,12 @@ import {
UserFromJSONTyped,
UserToJSON,
} from './User';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { ShoppingListRecipe } from './ShoppingListRecipe';
import {
ShoppingListRecipeFromJSON,
@@ -56,6 +62,12 @@ export interface PatchedShoppingListEntry {
* @memberof PatchedShoppingListEntry
*/
listRecipe?: number;
/**
*
* @type {Array<ShoppingList>}
* @memberof PatchedShoppingListEntry
*/
shoppingLists?: Array<ShoppingList>;
/**
*
* @type {Food}
@@ -155,6 +167,7 @@ export function PatchedShoppingListEntryFromJSONTyped(json: any, ignoreDiscrimin
'id': json['id'] == null ? undefined : json['id'],
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
'food': json['food'] == null ? undefined : FoodFromJSON(json['food']),
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
'amount': json['amount'] == null ? undefined : json['amount'],
@@ -179,6 +192,7 @@ export function PatchedShoppingListEntryToJSON(value?: Omit<PatchedShoppingListE
'id': value['id'],
'list_recipe': value['listRecipe'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
'food': FoodToJSON(value['food']),
'unit': UnitToJSON(value['unit']),
'amount': value['amount'],

View File

@@ -19,6 +19,12 @@ import {
UserFromJSONTyped,
UserToJSON,
} from './User';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { ShoppingListRecipe } from './ShoppingListRecipe';
import {
ShoppingListRecipeFromJSON,
@@ -56,6 +62,12 @@ export interface ShoppingListEntry {
* @memberof ShoppingListEntry
*/
listRecipe?: number;
/**
*
* @type {Array<ShoppingList>}
* @memberof ShoppingListEntry
*/
shoppingLists?: Array<ShoppingList>;
/**
*
* @type {Food}
@@ -161,6 +173,7 @@ export function ShoppingListEntryFromJSONTyped(json: any, ignoreDiscriminator: b
'id': json['id'] == null ? undefined : json['id'],
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
'food': FoodFromJSON(json['food']),
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
'amount': json['amount'],
@@ -185,6 +198,7 @@ export function ShoppingListEntryToJSON(value?: Omit<ShoppingListEntry, 'listRec
'id': value['id'],
'list_recipe': value['listRecipe'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
'food': FoodToJSON(value['food']),
'unit': UnitToJSON(value['unit']),
'amount': value['amount'],