Merge pull request #18 from lnbits/update_product_stock

Update product stock
This commit is contained in:
Vlad Stan 2023-03-16 15:29:54 +02:00 committed by GitHub
commit 45c7744282
5 changed files with 194 additions and 21 deletions

14
crud.py
View file

@ -287,6 +287,20 @@ async def update_product(merchant_id: str, product: Product) -> Product:
return updated_product
async def update_product_quantity(
product_id: str, new_quantity: int
) -> Optional[Product]:
await db.execute(
f"UPDATE nostrmarket.products SET quantity = ? WHERE id = ?",
(new_quantity, product_id),
)
row = await db.fetchone(
"SELECT * FROM nostrmarket.products WHERE id = ?",
(product_id,),
)
return Product.from_row(row) if row else None
async def get_product(merchant_id: str, product_id: str) -> Optional[Product]:
row = await db.fetchone(
"SELECT * FROM nostrmarket.products WHERE merchant_id =? AND id = ?",

View file

@ -1,5 +1,5 @@
import json
from typing import Optional
from typing import List, Optional, Tuple
from loguru import logger
@ -14,6 +14,8 @@ from .crud import (
get_products_by_ids,
get_wallet_for_product,
update_order_paid_status,
update_product,
update_product_quantity,
)
from .helpers import order_from_json
from .models import (
@ -21,11 +23,13 @@ from .models import (
Nostrable,
Order,
OrderExtra,
OrderItem,
OrderStatusUpdate,
PartialDirectMessage,
PartialOrder,
PaymentOption,
PaymentRequest,
Product,
)
from .nostr.event import NostrEvent
from .nostr.nostr_client import publish_nostr_event
@ -52,6 +56,13 @@ async def create_new_order(
wallet_id = await get_wallet_for_product(data.items[0].product_id)
assert wallet_id, "Missing wallet for order `{data.id}`"
product_ids = [i.product_id for i in data.items]
success, _, message = await compute_products_new_quantity(
merchant.id, product_ids, data.items
)
if not success:
return PaymentRequest(id=data.id, message=message, payment_options=[])
payment_hash, invoice = await create_invoice(
wallet_id=wallet_id,
amount=round(total_amount),
@ -95,20 +106,78 @@ async def handle_order_paid(order_id: str, merchant_pubkey: str):
try:
order = await update_order_paid_status(order_id, True)
assert order, f"Paid order cannot be found. Order id: {order_id}"
order_status = OrderStatusUpdate(
id=order_id, message="Payment received.", paid=True, shipped=order.shipped
)
merchant = await get_merchant_by_pubkey(merchant_pubkey)
assert merchant, f"Merchant cannot be found for order {order_id}"
# todo: lock
success, message = await update_products_for_order(merchant, order)
await notify_client_of_order_status(order, merchant, success, message)
except Exception as ex:
logger.warning(ex)
async def notify_client_of_order_status(
order: Order, merchant: Merchant, success: bool, message: str
):
dm_content = ""
if success:
order_status = OrderStatusUpdate(
id=order.id,
message="Payment received.",
paid=True,
shipped=order.shipped,
)
dm_content = json.dumps(
order_status.dict(), separators=(",", ":"), ensure_ascii=False
)
else:
dm_content = f"Order cannot be fulfilled. Reason: {message}"
dm_event = merchant.build_dm_event(dm_content, order.public_key)
await publish_nostr_event(dm_event)
except Exception as ex:
logger.warning(ex)
dm_event = merchant.build_dm_event(dm_content, order.public_key)
await publish_nostr_event(dm_event)
async def update_products_for_order(
merchant: Merchant, order: Order
) -> Tuple[bool, str]:
product_ids = [i.product_id for i in order.items]
success, products, message = await compute_products_new_quantity(
merchant.id, product_ids, order.items
)
if not success:
return success, message
for p in products:
product = await update_product_quantity(p.id, p.quantity)
event = await sign_and_send_to_nostr(merchant, product)
product.config.event_id = event.id
await update_product(merchant.id, product)
return True, "ok"
async def compute_products_new_quantity(
merchant_id: str, product_ids: List[str], items: List[OrderItem]
) -> Tuple[bool, List[Product], str]:
products: List[Product] = await get_products_by_ids(merchant_id, product_ids)
for p in products:
required_quantity = next(
(i.quantity for i in items if i.product_id == p.id), None
)
if not required_quantity:
return False, [], f"Product not found for order: {p.id}"
if p.quantity < required_quantity:
return (
False,
[],
f"Quantity not sufficient for product: {p.id}. Required {required_quantity} but only have {p.quantity}",
)
p.quantity -= required_quantity
return True, products, "ok"
async def process_nostr_message(msg: str):

View file

@ -342,8 +342,9 @@ async function customerStall(path) {
let json = JSON.parse(text)
if (json.id != this.activeOrder) return
if (json.payment_options) {
let payment_request = json.payment_options.find(o => o.type == 'ln')
.link
let payment_request = json.payment_options.find(
o => o.type == 'ln'
).link
if (!payment_request) return
this.loading = false
this.qrCodeDialog.data.payment_request = payment_request

View file

@ -78,10 +78,7 @@ const merchant = async () => {
message: 'Merchant Created!'
})
} catch (error) {
this.$q.notify({
type: 'negative',
message: `${error}`
})
LNbits.utils.notifyApiError(error)
}
},
getMerchant: async function () {

View file

@ -32,6 +32,7 @@ from .crud import (
delete_stall,
delete_zone,
get_direct_messages,
get_merchant_by_pubkey,
get_merchant_for_user,
get_order,
get_orders,
@ -78,6 +79,9 @@ async def api_create_merchant(
) -> Merchant:
try:
merchant = await get_merchant_by_pubkey(data.public_key)
assert merchant == None, "A merchant already uses this public key"
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant == None, "A merchant already exists for this user"
@ -85,6 +89,11 @@ async def api_create_merchant(
await subscribe_to_direct_messages(data.public_key, 0)
return merchant
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -128,6 +137,11 @@ async def api_delete_merchant(
await unsubscribe_from_direct_messages(merchant.public_key)
await delete_merchant(merchant.id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -145,6 +159,11 @@ async def api_get_zones(wallet: WalletTypeInfo = Depends(get_key_type)) -> List[
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
return await get_zones(merchant.id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -162,6 +181,11 @@ async def api_create_zone(
assert merchant, "Merchant cannot be found"
zone = await create_zone(merchant.id, data)
return zone
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -188,6 +212,11 @@ async def api_update_zone(
zone = await update_zone(merchant.id, data)
assert zone, "Cannot find updated zone"
return zone
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except HTTPException as ex:
raise ex
except Exception as ex:
@ -212,7 +241,11 @@ async def api_delete_zone(zone_id, wallet: WalletTypeInfo = Depends(require_admi
)
await delete_zone(merchant.id, zone_id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -242,7 +275,8 @@ async def api_create_stall(
await update_stall(merchant.id, stall)
return stall
except ValueError as ex:
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
@ -277,7 +311,7 @@ async def api_update_stall(
return stall
except HTTPException as ex:
raise ex
except ValueError as ex:
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
@ -302,6 +336,11 @@ async def api_get_stall(stall_id: str, wallet: WalletTypeInfo = Depends(get_key_
detail="Stall does not exist.",
)
return stall
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except HTTPException as ex:
raise ex
except Exception as ex:
@ -319,6 +358,11 @@ async def api_get_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
assert merchant, "Merchant cannot be found"
stalls = await get_stalls(merchant.id)
return stalls
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -337,6 +381,11 @@ async def api_get_stall_products(
assert merchant, "Merchant cannot be found"
products = await get_products(merchant.id, stall_id)
return products
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -355,6 +404,11 @@ async def api_get_stall_orders(
assert merchant, "Merchant cannot be found"
orders = await get_orders_for_stall(merchant.id, stall_id)
return orders
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -384,6 +438,11 @@ async def api_delete_stall(
stall.config.event_id = event.id
await update_stall(merchant.id, stall)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except HTTPException as ex:
raise ex
except Exception as ex:
@ -419,7 +478,7 @@ async def api_create_product(
await update_product(merchant.id, product)
return product
except ValueError as ex:
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
@ -451,14 +510,12 @@ async def api_update_product(
product.config.currency = stall.currency
product = await update_product(merchant.id, product)
event = await sign_and_send_to_nostr(merchant, product)
product.config.event_id = event.id
await update_product(merchant.id, product)
return product
except ValueError as ex:
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
@ -482,6 +539,11 @@ async def api_get_product(
products = await get_product(merchant.id, product_id)
return products
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -509,6 +571,11 @@ async def api_delete_product(
await delete_product(merchant.id, product_id)
await sign_and_send_to_nostr(merchant, product, True)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except HTTPException as ex:
raise ex
except Exception as ex:
@ -537,6 +604,11 @@ async def api_get_order(order_id: str, wallet: WalletTypeInfo = Depends(get_key_
detail="Order does not exist.",
)
return order
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except HTTPException as ex:
raise ex
except Exception as ex:
@ -555,6 +627,11 @@ async def api_get_orders(wallet: WalletTypeInfo = Depends(get_key_type)):
orders = await get_orders(merchant.id)
return orders
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -587,6 +664,11 @@ async def api_update_order_status(
return order
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -608,6 +690,11 @@ async def api_get_messages(
messages = await get_direct_messages(merchant.id, public_key)
return messages
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@ -632,6 +719,11 @@ async def api_create_message(
await publish_nostr_event(dm_event)
return dm
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
)
except Exception as ex:
logger.warning(ex)
raise HTTPException(