feat: update shipping zone when re-issuing invoice

This commit is contained in:
Vlad Stan 2023-07-04 18:13:17 +03:00
parent b5aed1224a
commit 02d9fcfbfd
5 changed files with 100 additions and 30 deletions

View file

@ -45,6 +45,7 @@ class MerchantConfig(MerchantProfile):
active: bool = False active: bool = False
restore_in_progress: Optional[bool] = False restore_in_progress: Optional[bool] = False
class PartialMerchant(BaseModel): class PartialMerchant(BaseModel):
private_key: str private_key: str
public_key: str public_key: str
@ -400,6 +401,11 @@ class OrderStatusUpdate(BaseModel):
shipped: Optional[bool] shipped: Optional[bool]
class OrderReissue(BaseModel):
id: str
shipping_id: Optional[str] = None
class PaymentOption(BaseModel): class PaymentOption(BaseModel):
type: str type: str
link: str link: str
@ -414,14 +420,15 @@ class PaymentRequest(BaseModel):
######################################## MESSAGE ######################################## ######################################## MESSAGE ########################################
class DirectMessageType(Enum): class DirectMessageType(Enum):
"""Various types os direct messages.""" """Various types os direct messages."""
PLAIN_TEXT = -1 PLAIN_TEXT = -1
CUSTOMER_ORDER = 0 CUSTOMER_ORDER = 0
PAYMENT_REQUEST = 1 PAYMENT_REQUEST = 1
ORDER_PAID_OR_SHIPPED = 2 ORDER_PAID_OR_SHIPPED = 2
class PartialDirectMessage(BaseModel): class PartialDirectMessage(BaseModel):
event_id: Optional[str] event_id: Optional[str]
event_created_at: Optional[int] event_created_at: Optional[int]
@ -437,7 +444,7 @@ class PartialDirectMessage(BaseModel):
msg_json = json.loads(msg) msg_json = json.loads(msg)
if "type" in msg_json: if "type" in msg_json:
return DirectMessageType(msg_json["type"]), msg_json return DirectMessageType(msg_json["type"]), msg_json
return DirectMessageType.PLAIN_TEXT, None return DirectMessageType.PLAIN_TEXT, None
except Exception: except Exception:
return DirectMessageType.PLAIN_TEXT, None return DirectMessageType.PLAIN_TEXT, None
@ -452,7 +459,6 @@ class DirectMessage(PartialDirectMessage):
return dm return dm
######################################## CUSTOMERS ######################################## ######################################## CUSTOMERS ########################################
@ -471,5 +477,7 @@ class Customer(BaseModel):
@classmethod @classmethod
def from_row(cls, row: Row) -> "Customer": def from_row(cls, row: Row) -> "Customer":
customer = cls(**dict(row)) customer = cls(**dict(row))
customer.profile = CustomerProfile(**json.loads(row["meta"])) if "meta" in row else None customer.profile = (
CustomerProfile(**json.loads(row["meta"])) if "meta" in row else None
)
return customer return customer

View file

@ -275,7 +275,6 @@ async def process_nostr_message(msg: str):
if type.upper() == "EVENT": if type.upper() == "EVENT":
subscription_id, event = rest subscription_id, event = rest
event = NostrEvent(**event) event = NostrEvent(**event)
print("kind: ", event.kind, ": ", msg)
if event.kind == 0: if event.kind == 0:
await _handle_customer_profile_update(event) await _handle_customer_profile_update(event)
elif event.kind == 4: elif event.kind == 4:

View file

@ -121,13 +121,6 @@
</div> </div>
<div class="col-3 col-sm-1"></div> <div class="col-3 col-sm-1"></div>
</div> </div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg">Address:</div>
<div class="col-6 col-sm-8 q-pr-lg">
<q-input filled dense readonly disabled v-model.trim="props.row.address" type="text"></q-input>
</div>
<div class="col-3 col-sm-1"></div>
</div>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg">Customer Public Key:</div> <div class="col-3 q-pr-lg">Customer Public Key:</div>
@ -137,6 +130,14 @@
<div class="col-3 col-sm-1"></div> <div class="col-3 col-sm-1"></div>
</div> </div>
<div v-if="props.row.address" class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg">Address:</div>
<div class="col-6 col-sm-8 q-pr-lg">
<q-input filled dense readonly disabled v-model.trim="props.row.address" type="text"></q-input>
</div>
<div class="col-3 col-sm-1"></div>
</div>
<div v-if="props.row.contact.phone" class="row items-center no-wrap q-mb-md"> <div v-if="props.row.contact.phone" class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg">Phone:</div> <div class="col-3 q-pr-lg">Phone:</div>
<div class="col-6 col-sm-8 q-pr-lg"> <div class="col-6 col-sm-8 q-pr-lg">
@ -151,20 +152,29 @@
</div> </div>
<div class="col-3 col-sm-1"></div> <div class="col-3 col-sm-1"></div>
</div> </div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg">Shipping Zone:</div>
<div class="col-6 col-sm-8 q-pr-lg">
<q-select :options="getStallZones(props.row.stall_id)" filled dense emit-value
v-model.trim="props.row.shipping_id" label="Shipping Zones"></q-select>
</div>
<div class="col-3 col-sm-1"></div>
</div>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg">Invoice ID:</div> <div class="col-3 q-pr-lg">Invoice ID:</div>
<div class="col-6 col-sm-8 q-pr-lg"> <div class="col-6 col-sm-8 q-pr-lg">
<q-input filled dense readonly disabled v-model.trim="props.row.invoice_id" type="text"></q-input> <q-input filled dense readonly disabled v-model.trim="props.row.invoice_id" type="text"></q-input>
</div> </div>
<div class="col-3"> <div class="col-3">
</div> </div>
</div> </div>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col-3 q-pr-lg"></div> <div class="col-3 q-pr-lg"></div>
<div class="col-9"> <div class="col-9">
<q-btn @click="reissueOrderInvoice(props.row.id)" unelevated color="primary" type="submit" class="float-left" label="Reissue Invoice"></q-btn> <q-btn @click="reissueOrderInvoice(props.row)" unelevated color="primary" type="submit"
class="float-left" label="Reissue Invoice"></q-btn>
</div> </div>
</div> </div>
</q-td> </q-td>

View file

@ -17,6 +17,7 @@ async function orderList(path) {
data: function () { data: function () {
return { return {
orders: [], orders: [],
stalls: [],
selectedOrder: null, selectedOrder: null,
shippingMessage: '', shippingMessage: '',
showShipDialog: false, showShipDialog: false,
@ -48,6 +49,7 @@ async function orderList(path) {
id: 'false' id: 'false'
} }
], ],
zoneOptions: [],
ordersTable: { ordersTable: {
columns: [ columns: [
{ {
@ -205,22 +207,26 @@ async function orderList(path) {
this.search.restoring = false this.search.restoring = false
} }
}, },
reissueOrderInvoice: async function (orderId) { reissueOrderInvoice: async function (order) {
try { try {
const { data } = await LNbits.api.request( const { data } = await LNbits.api.request(
'PUT', 'PUT',
`/nostrmarket/api/v1/order/${orderId}/reissue`, `/nostrmarket/api/v1/order/reissue`,
this.adminkey this.adminkey,
{
id: order.id,
shipping_id: order.shipping_id
}
) )
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: 'Order invoice reissued!' message: 'Order invoice reissued!'
}) })
data.expanded = order.expanded
const i = this.orders.map(o => o.id).indexOf(orderId) const i = this.orders.map(o => o.id).indexOf(order.id)
console.log('### order', i)
if (i !== -1) { if (i !== -1) {
this.orders[i] = { ...this.orders[i], ...data} this.orders[i] = { ...this.orders[i], ...data }
} }
} catch (error) { } catch (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
@ -265,6 +271,44 @@ async function orderList(path) {
order.isNew = false order.isNew = false
this.orders = [order] this.orders = [order]
}, },
getZones: async function () {
try {
const { data } = await LNbits.api.request(
'GET',
'/nostrmarket/api/v1/zone',
this.inkey
)
return data.map(z => ({
id: z.id,
value: z.id,
label: z.name
? `${z.name} (${z.countries.join(', ')})`
: z.countries.join(', ')
}))
} catch (error) {
LNbits.utils.notifyApiError(error)
}
return []
},
getStalls: async function (pending = false) {
try {
const { data } = await LNbits.api.request(
'GET',
`/nostrmarket/api/v1/stall?pending=${pending}`,
this.inkey
)
return data.map(s => ({ ...s, expanded: false }))
} catch (error) {
LNbits.utils.notifyApiError(error)
}
return []
},
getStallZones: function (stallId) {
const stall = this.stalls.find(s => s.id === stallId)
if (!stall) return []
return this.zoneOptions.filter(z => stall.shipping_zones.find(s => s.id === z.id))
},
showShipOrderDialog: function (order) { showShipOrderDialog: function (order) {
this.selectedOrder = order this.selectedOrder = order
this.shippingMessage = order.shipped this.shippingMessage = order.shipped
@ -312,6 +356,8 @@ async function orderList(path) {
await this.getOrders() await this.getOrders()
} }
await this.getCustomers() await this.getCustomers()
this.zoneOptions = await this.getZones()
this.stalls = await this.getStalls()
} }
}) })
} }

View file

@ -66,6 +66,7 @@ from .models import (
DirectMessageType, DirectMessageType,
Merchant, Merchant,
Order, Order,
OrderReissue,
OrderStatusUpdate, OrderStatusUpdate,
PartialDirectMessage, PartialDirectMessage,
PartialMerchant, PartialMerchant,
@ -864,31 +865,37 @@ async def api_restore_orders(
) )
@nostrmarket_ext.put("/api/v1/order/{order_id}/reissue") @nostrmarket_ext.put("/api/v1/order/reissue")
async def api_reissue_order_invoice( async def api_reissue_order_invoice(
order_id: str, reissue_data: OrderReissue,
wallet: WalletTypeInfo = Depends(require_admin_key), wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Order: ) -> Order:
try: try:
merchant = await get_merchant_for_user(wallet.wallet.user) merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found" assert merchant, "Merchant cannot be found"
data = await get_order(merchant.id, order_id) data = await get_order(merchant.id, reissue_data.id)
assert data, "Order cannot be found" assert data, "Order cannot be found"
if reissue_data.shipping_id:
data.shipping_id = reissue_data.shipping_id
order, invoice = await build_order_with_payment( order, invoice = await build_order_with_payment(
merchant.id, merchant.public_key, PartialOrder(**data.dict()) merchant.id, merchant.public_key, PartialOrder(**data.dict())
) )
order_update = {
"stall_id": order.stall_id,
"total": order.total,
"invoice_id": order.invoice_id,
"shipping_id": order.shipping_id,
"extra_data": json.dumps(order.extra.dict()),
}
await update_order( await update_order(
merchant.id, merchant.id,
order.id, order.id,
**{ **order_update,
"stall_id": order.stall_id,
"total": order.total,
"invoice_id": order.invoice_id,
"extra_data": json.dumps(order.extra.dict()),
},
) )
payment_req = PaymentRequest( payment_req = PaymentRequest(
id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)] id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)]