Skip to content

Commit

Permalink
Various improvements (#326)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkjellid committed Jun 20, 2024
1 parent fd52df2 commit 0f53598
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 60 deletions.
42 changes: 23 additions & 19 deletions frontend/apps/products/components/ProductOdaImportDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Badge } from '@mantine/core'
import { Alert, Badge } from '@mantine/core'
import { useState } from 'react'

import { Button, type ButtonProps } from '../../../components/Button'
Expand All @@ -9,8 +9,8 @@ import { useForm } from '../../../hooks/forms'
import { useCommonStyles } from '../../../styles/common'
import {
type ProductOdaImportForm,
type OdaProductDetailRecord,
type OdaProductDetailRecordAPIResponse,
type ProductOdaImportOutAPIResponse,
type ProductOdaImportOut,
} from '../../../types'
import { urls } from '../../urls'

Expand All @@ -26,7 +26,7 @@ function ProductOdaImportDrawer({ opened, onClose, refetch }: ProductOdaImportDr
***********/
const { classes } = useCommonStyles()
const form = useForm<ProductOdaImportForm>({ key: 'ProductOdaImportForm' })
const [fetchedProduct, setFetchedProduct] = useState<OdaProductDetailRecord | null>()
const [fetchedProductData, setFetchedProductData] = useState<ProductOdaImportOut | null>()
const [importLoadingState, setImportLoadingState] =
useState<ButtonProps['loadingState']>('initial')

Expand All @@ -37,20 +37,20 @@ function ProductOdaImportDrawer({ opened, onClose, refetch }: ProductOdaImportDr
const close = () => {
onClose()
form.resetForm()
setFetchedProduct(null)
setFetchedProductData(null)
}

const fetchOdaProduct = async () => {
try {
form.setLoadingState('loading')
const response = await performPost<OdaProductDetailRecordAPIResponse>({
const response = await performPost<ProductOdaImportOutAPIResponse>({
url: urls.products.oda.import(),
...form.buildPayload(),
})
form.setLoadingState('success')

if (response && response.data) {
setFetchedProduct(response.data)
setFetchedProductData(response.data)
}
} catch (e) {
const errorResponse = (e as any).response.data
Expand All @@ -62,12 +62,12 @@ function ProductOdaImportDrawer({ opened, onClose, refetch }: ProductOdaImportDr
}

const importOdaProduct = async () => {
if (!fetchedProduct) return
if (!fetchedProductData) return
try {
setImportLoadingState('loading')
await performPost({
url: urls.products.oda.importConfirm(),
data: { odaProductId: fetchedProduct.id },
data: { odaProductId: fetchedProductData.product.id },
})
setImportLoadingState('success')
close()
Expand All @@ -94,7 +94,7 @@ function ProductOdaImportDrawer({ opened, onClose, refetch }: ProductOdaImportDr
</Button>
<Button
loadingState={importLoadingState}
disabled={!fetchedProduct}
disabled={!fetchedProductData || fetchedProductData.hasBeenImportedPreviously}
onClick={() => importOdaProduct()}
>
Import product
Expand All @@ -119,23 +119,27 @@ function ProductOdaImportDrawer({ opened, onClose, refetch }: ProductOdaImportDr
},
}}
/>
{fetchedProduct && (
<div className="mt-6">
{fetchedProductData && (
<div className="mt-6 space-y-6">
{fetchedProductData.hasBeenImportedPreviously && (
<Alert color="red">This product is already imported.</Alert>
)}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-6">
<img
className={`object-contain w-16 h-16 p-1 border-2 ${classes.border} border-solid rounded-lg bg-white`}
src={fetchedProduct.images[0].thumbnail.url}
src={fetchedProductData.product.images[0].thumbnail.url}
alt=""
/>
<div className="w-80 overflow-hidden">
<div className="whitespace-nowrap text-ellipsis overflow-hidden text-lg font-semibold leading-6">
{fetchedProduct.fullName},{' '}
{Number(fetchedProduct.grossPrice) / Number(fetchedProduct.grossUnitPrice)}{' '}
{fetchedProduct.unitPriceQuantityAbbreviation}
{fetchedProductData.product.fullName},{' '}
{Number(fetchedProductData.product.grossPrice) /
Number(fetchedProductData.product.grossUnitPrice)}{' '}
{fetchedProductData.product.unitPriceQuantityAbbreviation}
</div>
{fetchedProduct.brand ? (
<div className="mt-1 text-sm">{fetchedProduct.brand}</div>
{fetchedProductData.product.brand ? (
<div className="mt-1 text-sm">{fetchedProductData.product.brand}</div>
) : (
<div className="mt-1 text-sm">Unknown supplier</div>
)}
Expand All @@ -145,7 +149,7 @@ function ProductOdaImportDrawer({ opened, onClose, refetch }: ProductOdaImportDr
<Badge size="lg" color="green">
Available
</Badge>
<div className="mt-2 text-sm">{fetchedProduct.grossPrice} kr</div>
<div className="mt-2 text-sm">{fetchedProductData.product.grossPrice} kr</div>
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion frontend/types/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export type { OdaProductCategoryRecord } from './models/OdaProductCategoryRecord
export type { OdaProductClassifierRecord } from './models/OdaProductClassifierRecord';
export { OdaProductDetailedInfo } from './models/OdaProductDetailedInfo';
export { OdaProductDetailRecord } from './models/OdaProductDetailRecord';
export { OdaProductDetailRecordAPIResponse } from './models/OdaProductDetailRecordAPIResponse';
export type { OdaProductDiscountRecord } from './models/OdaProductDiscountRecord';
export type { OdaProductHazardRecord } from './models/OdaProductHazardRecord';
export type { OdaProductHazardSafetyDataSheetRecord } from './models/OdaProductHazardSafetyDataSheetRecord';
Expand All @@ -45,6 +44,8 @@ export type { ProductEditForm } from './models/ProductEditForm';
export type { ProductEditIn } from './models/ProductEditIn';
export type { ProductOdaImportConfirmIn } from './models/ProductOdaImportConfirmIn';
export type { ProductOdaImportForm } from './models/ProductOdaImportForm';
export type { ProductOdaImportOut } from './models/ProductOdaImportOut';
export { ProductOdaImportOutAPIResponse } from './models/ProductOdaImportOutAPIResponse';
export type { ProductRecord } from './models/ProductRecord';
export { ProductRecordAPIResponse } from './models/ProductRecordAPIResponse';
export { ProductRecordListAPIResponse } from './models/ProductRecordListAPIResponse';
Expand Down

This file was deleted.

12 changes: 12 additions & 0 deletions frontend/types/generated/models/ProductOdaImportOut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import type { OdaProductDetailRecord } from './OdaProductDetailRecord';

export type ProductOdaImportOut = {
product: OdaProductDetailRecord;
hasBeenImportedPreviously: boolean;
};

23 changes: 23 additions & 0 deletions frontend/types/generated/models/ProductOdaImportOutAPIResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import type { ProductOdaImportOut } from './ProductOdaImportOut';

export type ProductOdaImportOutAPIResponse = {
status: ProductOdaImportOutAPIResponse.status;
message?: string;
data?: ProductOdaImportOut;
};

export namespace ProductOdaImportOutAPIResponse {

export enum status {
SUCCESS = 'success',
ERROR = 'error',
}


}

1 change: 1 addition & 0 deletions frontend/types/generated/models/RecipeIngredientRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export type RecipeIngredientRecord = {
id: number;
title: string;
product?: ProductRecord;
isBaseIngredient?: boolean;
};

4 changes: 3 additions & 1 deletion nest/products/core/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ def get_products(
if oda_ids:
filters["oda_id__in"] = oda_ids

products = Product.objects.filter(**filters).select_related("unit").distinct("id")
products = (
Product.objects.filter(**filters).select_related("unit").order_by("-created_at")
)
ids = [product.id for product in products]

log_entries = get_log_entries_for_objects(model=Product, ids=ids, limit=10)
Expand Down
23 changes: 18 additions & 5 deletions nest/products/oda/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,38 @@
from nest.api.responses import APIResponse
from nest.core.decorators import staff_required

from .clients import OdaClient
from .forms import ProductOdaImportForm
from .records import OdaProductDetailRecord
from .selectors import retrieve_product_from_oda
from .services import import_product_from_oda

router = Router(tags=["Oda products"])


@router.post("import/", response=APIResponse[OdaProductDetailRecord])
class ProductOdaImportOut(Schema):
product: OdaProductDetailRecord
has_been_imported_previously: bool


@router.post("import/", response=APIResponse[ProductOdaImportOut])
@staff_required
def product_oda_import_api(
request: HttpRequest, payload: ProductOdaImportForm
) -> APIResponse[OdaProductDetailRecord]:
) -> APIResponse[ProductOdaImportOut]:
"""
Import product data from id. Note: This does not create a product, it only retrieves
data.
"""
oda_product = OdaClient.get_product(product_id=payload.oda_product_id)
return APIResponse(status="success", data=oda_product)
oda_product, is_imported = retrieve_product_from_oda(
oda_product_id=payload.oda_product_id
)
return APIResponse(
status="success",
data=ProductOdaImportOut(
product=oda_product,
has_been_imported_previously=is_imported,
),
)


class ProductOdaImportConfirmIn(Schema):
Expand Down
17 changes: 17 additions & 0 deletions nest/products/oda/selectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from nest.products.core.models import Product
from nest.products.oda.clients import OdaClient
from nest.products.oda.records import OdaProductDetailRecord

HasBeenImportedPreviously = bool


def retrieve_product_from_oda(
*, oda_product_id: int | str
) -> tuple[OdaProductDetailRecord, HasBeenImportedPreviously]:
"""
Retrieve product from Oda and check if it has been imported previously.
"""
oda_product = OdaClient.get_product(product_id=oda_product_id)
has_been_imported = Product.objects.filter(oda_id=oda_product_id).exists()

return oda_product, has_been_imported
3 changes: 1 addition & 2 deletions nest/products/oda/services.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import math
from decimal import Decimal

import structlog
Expand Down Expand Up @@ -80,7 +79,7 @@ def get_product_image() -> File | None: # type: ignore
"gross_price": product_response.gross_price,
"gross_unit_price": product_response.gross_unit_price,
"unit_id": converted_unit.id,
"unit_quantity": math.ceil(converted_quantity),
"unit_quantity": round(converted_quantity),
"is_available": product_response.availability.is_available,
"supplier": product_response.brand,
"thumbnail": get_product_image(),
Expand Down
6 changes: 4 additions & 2 deletions nest/recipes/ingredients/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ def get_recipe_ingredients() -> list[RecipeIngredientRecord]:
"""
Get a list of all ingredients in the application.
"""
ingredients = RecipeIngredient.objects.all().select_related(
"product", "product__unit"
ingredients = (
RecipeIngredient.objects.all()
.select_related("product", "product__unit")
.order_by("-created_at")
)
records = [
RecipeIngredientRecord.from_db_model(ingredient) for ingredient in ingredients
Expand Down
25 changes: 21 additions & 4 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OdaProductDetailRecordAPIResponse"
"$ref": "#/components/schemas/ProductOdaImportOutAPIResponse"
}
}
}
Expand Down Expand Up @@ -2011,8 +2011,25 @@
}
}
},
"OdaProductDetailRecordAPIResponse": {
"title": "OdaProductDetailRecordAPIResponse",
"ProductOdaImportOut": {
"title": "ProductOdaImportOut",
"type": "object",
"required": [
"product",
"hasBeenImportedPreviously"
],
"properties": {
"product": {
"$ref": "#/components/schemas/OdaProductDetailRecord"
},
"hasBeenImportedPreviously": {
"title": "Has Been Imported Previously",
"type": "boolean"
}
}
},
"ProductOdaImportOutAPIResponse": {
"title": "ProductOdaImportOutAPIResponse",
"type": "object",
"required": [
"status"
Expand All @@ -2031,7 +2048,7 @@
"type": "string"
},
"data": {
"$ref": "#/components/schemas/OdaProductDetailRecord"
"$ref": "#/components/schemas/ProductOdaImportOut"
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions tests/products/test_core_selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_selector_get_products(
assert {p.id for p in all_products} == set(normal_product_ids + oda_product_ids)
log_entries_mock.assert_called_once_with(
model=Product,
ids=oda_product_ids + normal_product_ids,
ids=list(reversed(list(oda_product_ids + normal_product_ids))),
limit=10,
)
log_entries_mock.reset_mock()
Expand All @@ -48,7 +48,7 @@ def test_selector_get_products(
assert {p.id for p in normal_products} == set(normal_product_ids)
log_entries_mock.assert_called_once_with(
model=Product,
ids=normal_product_ids,
ids=list(reversed(normal_product_ids)),
limit=10,
)
log_entries_mock.reset_mock()
Expand All @@ -59,7 +59,7 @@ def test_selector_get_products(
assert {p.id for p in oda_products} == set(oda_product_ids)
log_entries_mock.assert_called_once_with(
model=Product,
ids=oda_product_ids,
ids=list(reversed(oda_product_ids)),
limit=10,
)

Expand Down

0 comments on commit 0f53598

Please sign in to comment.