feat: Add functionality to move inventory items to pantry and enhance pantry management
Test Suite / test (24.15.0) (push) Has been cancelled

- Implemented moveInventoryItemToPantry method in InventoryRepository to facilitate moving items from inventory to pantry.
- Enhanced InventoryScreen with a new header section providing context about the inventory.
- Added a button in SwipeableInventoryTile to move items to pantry with appropriate error handling.
- Introduced movePantryItemToInventory method in PantryRepository to support moving items back to inventory.
- Refactored PantryScreen to rename _addToInventory to _moveToInventory for clarity and updated UI to reflect changes.
- Added AdminPantryItem model to represent pantry items in the admin panel.
- Created AdminPantryPanel for managing pantry items, including moving items to inventory and listing users.
- Developed AdminPrivateProductsPanel for managing private products, allowing promotion to global products.
This commit is contained in:
Nils-Johan Gynther
2026-05-11 09:06:30 +02:00
parent edf9c74e75
commit 84ccabe2fe
27 changed files with 1851 additions and 376 deletions
+36 -1
View File
@@ -1,7 +1,9 @@
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
import { PantryService } from './pantry.service';
import { CreatePantryItemDto } from './dto/create-pantry-item.dto';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { Roles } from '../auth/decorators/roles.decorator';
import { CreateInventoryDto } from '../inventory/dto/create-inventory.dto';
@Controller('pantry')
export class PantryController {
@@ -20,6 +22,15 @@ export class PantryController {
return this.pantryService.create(user.userId, body);
}
@Roles('admin')
@Get('admin')
findAllAdmin(@Query('userId') userIdRaw?: string) {
const userId = userIdRaw == null || userIdRaw.trim() === '' ? undefined : Number(userIdRaw);
return this.pantryService.findAllAdmin({
userId: Number.isFinite(userId as number) ? (userId as number) : undefined,
});
}
@Delete(':id')
remove(
@CurrentUser() user: { userId: number },
@@ -27,4 +38,28 @@ export class PantryController {
) {
return this.pantryService.remove(user.userId, id);
}
@Roles('admin')
@Delete('admin/:id')
removeAdmin(@Param('id', ParseIntPipe) id: number) {
return this.pantryService.removeAdmin(id);
}
@Post(':id/move-to-inventory')
moveToInventory(
@CurrentUser() user: { userId: number },
@Param('id', ParseIntPipe) id: number,
@Body() body: CreateInventoryDto,
) {
return this.pantryService.moveToInventory(user.userId, id, body);
}
@Roles('admin')
@Post('admin/:id/move-to-inventory')
moveToInventoryAdmin(
@Param('id', ParseIntPipe) id: number,
@Body() body: CreateInventoryDto,
) {
return this.pantryService.moveToInventoryAdmin(id, body);
}
}
+103 -1
View File
@@ -1,6 +1,12 @@
import { Injectable, ConflictException, NotFoundException } from '@nestjs/common';
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreatePantryItemDto } from './dto/create-pantry-item.dto';
import { CreateInventoryDto } from '../inventory/dto/create-inventory.dto';
import { Prisma } from '@prisma/client';
type PantryQuery = {
userId?: number;
};
@Injectable()
export class PantryService {
@@ -18,6 +24,38 @@ export class PantryService {
});
}
findAllAdmin(query?: PantryQuery) {
return this.prisma.pantryItem.findMany({
where: typeof query?.userId === 'number' ? { userId: query.userId } : {},
include: {
user: {
select: {
id: true,
username: true,
email: true,
},
},
product: {
include: {
categoryRef: {
include: {
parent: {
include: {
parent: true,
},
},
},
},
},
},
},
orderBy: [
{ user: { username: 'asc' } },
{ product: { name: 'asc' } },
],
});
}
async create(userId: number, data: CreatePantryItemDto) {
const existing = await this.prisma.pantryItem.findUnique({
where: {
@@ -53,4 +91,68 @@ export class PantryService {
return this.prisma.pantryItem.delete({ where: { id } });
}
async removeAdmin(id: number) {
const item = await this.prisma.pantryItem.findUnique({ where: { id } });
if (!item) {
throw new NotFoundException(`PantryItem med id ${id} hittades inte`);
}
return this.prisma.pantryItem.delete({ where: { id } });
}
private async movePantryItemToInventoryCore(
item: { id: number; userId: number; productId: number; location: string | null },
data: CreateInventoryDto,
) {
return this.prisma.$transaction(async (tx) => {
const inventoryItem = await tx.inventoryItem.create({
data: {
userId: item.userId,
productId: item.productId,
quantity: new Prisma.Decimal(data.quantity),
unit: data.unit.trim(),
location: data.location?.trim() || item.location || undefined,
purchaseDate: data.purchaseDate ? new Date(data.purchaseDate) : undefined,
bestBeforeDate: data.bestBeforeDate ? new Date(data.bestBeforeDate) : undefined,
brand: data.brand?.trim() || undefined,
origin: data.origin?.trim() || undefined,
receiptName: data.receiptName?.trim() || undefined,
opened: data.opened ?? false,
suitableFor: data.suitableFor?.trim() || undefined,
comment: data.comment?.trim() || undefined,
},
include: { product: true },
});
await tx.pantryItem.delete({ where: { id: item.id } });
return inventoryItem;
});
}
async moveToInventory(userId: number, pantryItemId: number, data: CreateInventoryDto) {
const item = await this.prisma.pantryItem.findFirst({
where: { id: pantryItemId, userId },
});
if (!item) {
throw new NotFoundException(`PantryItem med id ${pantryItemId} hittades inte`);
}
return this.movePantryItemToInventoryCore(item, data);
}
async moveToInventoryAdmin(pantryItemId: number, data: CreateInventoryDto) {
const item = await this.prisma.pantryItem.findUnique({
where: { id: pantryItemId },
});
if (!item) {
throw new NotFoundException(`PantryItem med id ${pantryItemId} hittades inte`);
}
return this.movePantryItemToInventoryCore(item, data);
}
}