Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@
db = Database("ext_tpos")


def _serialize_inventory_tags(tags: list[str] | str | None) -> str | None:
if isinstance(tags, list):
return ",".join([tag for tag in tags if tag])
return tags


async def create_tpos(data: CreateTposData) -> Tpos:
tpos_id = urlsafe_short_hash()
tpos = Tpos(id=tpos_id, **data.dict())
data_dict = data.dict()
data_dict["inventory_tags"] = _serialize_inventory_tags(data.inventory_tags)
data_dict["inventory_omit_tags"] = _serialize_inventory_tags(
data.inventory_omit_tags
)
tpos = Tpos(id=tpos_id, **data_dict)
await db.insert("tpos.pos", tpos)
return tpos

Expand Down Expand Up @@ -46,6 +57,8 @@ async def get_clean_tpos(tpos_id: str) -> TposClean | None:


async def update_tpos(tpos: Tpos) -> Tpos:
tpos.inventory_tags = _serialize_inventory_tags(tpos.inventory_tags)
tpos.inventory_omit_tags = _serialize_inventory_tags(tpos.inventory_omit_tags)
await db.update("tpos.pos", tpos)
return tpos

Expand Down
32 changes: 32 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,35 @@ async def m015_addfiat(db: Database):
ALTER TABLE tpos.pos ADD stripe_card_payments BOOLEAN DEFAULT false;
"""
)


async def m016_add_inventory_settings(db: Database):
"""
Add inventory integration columns
"""
await db.execute(
"""
ALTER TABLE tpos.pos ADD use_inventory BOOLEAN DEFAULT false;
"""
)
await db.execute(
"""
ALTER TABLE tpos.pos ADD inventory_id TEXT NULL;
"""
)
await db.execute(
"""
ALTER TABLE tpos.pos ADD inventory_tags TEXT NULL;
"""
)


async def m017_add_inventory_omit_tags(db: Database):
"""
Add inventory omit tags column
"""
await db.execute(
"""
ALTER TABLE tpos.pos ADD inventory_omit_tags TEXT NULL;
"""
)
31 changes: 30 additions & 1 deletion models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from time import time

from fastapi import Query
Expand All @@ -17,6 +19,7 @@ class CreateTposInvoice(BaseModel):
memo: str | None = Query(None)
exchange_rate: float | None = Query(None, ge=0.0)
details: dict | None = Query(None)
inventory: InventorySale | None = Query(None)
tip_amount: int | None = Query(None, ge=1)
user_lnaddress: str | None = Query(None)
internal_memo: str | None = Query(None, max_length=512)
Expand All @@ -26,12 +29,27 @@ class CreateTposInvoice(BaseModel):
tip_amount_fiat: float | None = Query(None, ge=0.0)


class InventorySaleItem(BaseModel):
id: str
quantity: int = Field(1, ge=1)


class InventorySale(BaseModel):
inventory_id: str
tags: list[str] = Field(default_factory=list)
items: list[InventorySaleItem] = Field(default_factory=list)


class CreateTposData(BaseModel):
wallet: str | None
name: str | None
currency: str | None
use_inventory: bool = Field(False)
inventory_id: str | None = None
inventory_tags: list[str] | None = None
inventory_omit_tags: list[str] | None = None
tax_inclusive: bool = Field(True)
tax_default: float = Field(0.0)
tax_default: float | None = Field(0.0)
tip_options: str = Field("[]")
tip_wallet: str = Field("")
withdraw_time: int = Field(0)
Expand All @@ -48,6 +66,10 @@ class CreateTposData(BaseModel):
fiat_provider: str | None = Field(None)
stripe_card_payments: bool = False

@validator("tax_default", pre=True, always=True)
def default_tax_when_none(cls, v):
return 0.0 if v is None else v


class TposClean(BaseModel):
id: str
Expand All @@ -64,6 +86,10 @@ class TposClean(BaseModel):
lnaddress: bool | None = None
lnaddress_cut: int = 0
items: str | None = None
use_inventory: bool = False
inventory_id: str | None = None
inventory_tags: str | None = None
inventory_omit_tags: str | None = None
tip_options: str | None = None
enable_receipt_print: bool
business_name: str | None = None
Expand Down Expand Up @@ -129,3 +155,6 @@ class TapToPay(BaseModel):
tpos_id: str | None = None
payment_hash: str | None = None
paid: bool = False


CreateTposInvoice.update_forward_refs(InventorySale=InventorySale)
134 changes: 133 additions & 1 deletion static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ const mapTpos = obj => {
obj.tpos
].join('')
obj.items = obj.items ? JSON.parse(obj.items) : []
obj.use_inventory = Boolean(obj.use_inventory)
obj.inventory_id = obj.inventory_id || null
const tagString =
obj.inventory_tags === 'null' ? '' : obj.inventory_tags || ''
obj.inventory_tags = tagString ? tagString.split(',').filter(Boolean) : []
const omitTagString =
obj.inventory_omit_tags === 'null' ? '' : obj.inventory_omit_tags || ''
obj.inventory_omit_tags = omitTagString
? omitTagString.split(',').filter(Boolean)
: []
obj.itemsMap = new Map()
obj.items.forEach((item, idx) => {
let id = `${obj.id}:${idx + 1}`
Expand All @@ -28,6 +38,12 @@ window.app = Vue.createApp({
currencyOptions: [],
hasFiatProvider: false,
fiatProviders: null,
inventoryStatus: {
enabled: false,
inventory_id: null,
tags: [],
omit_tags: []
},
tpossTable: {
columns: [
{name: 'name', align: 'left', label: 'Name', field: 'name'},
Expand Down Expand Up @@ -80,6 +96,9 @@ window.app = Vue.createApp({
formDialog: {
show: false,
data: {
use_inventory: false,
inventory_id: null,
inventory_tags: [],
tip_options: [],
withdraw_between: 10,
withdraw_time_option: '',
Expand Down Expand Up @@ -182,12 +201,38 @@ window.app = Vue.createApp({
!data.wallet ||
(this.formDialog.advanced.otc && !data.withdraw_limit)
)
},
inventoryModeOptions() {
return [
{
label: 'Use inventory extension',
value: true,
disable: !this.inventoryStatus.enabled
},
{label: 'Use TPoS items', value: false}
]
},
inventoryTagOptions() {
return (this.inventoryStatus.tags || []).map(tag => ({
label: tag,
value: tag
}))
},
inventoryOmitTagOptions() {
return (this.inventoryStatus.omit_tags || []).map(tag => ({
label: tag,
value: tag
}))
}
},
methods: {
closeFormDialog() {
this.formDialog.show = false
this.formDialog.data = {
use_inventory: false,
inventory_id: this.inventoryStatus.inventory_id,
inventory_tags: [...(this.inventoryStatus.tags || [])],
inventory_omit_tags: [...(this.inventoryStatus.omit_tags || [])],
tip_options: [],
withdraw_between: 10,
withdraw_time_option: '',
Expand All @@ -213,6 +258,25 @@ window.app = Vue.createApp({
})
})
},
async loadInventoryStatus() {
if (!this.g.user.wallets.length) return
try {
const {data} = await LNbits.api.request(
'GET',
'/tpos/api/v1/inventory/status',
this.g.user.wallets[0].adminkey
)
this.inventoryStatus = data
// Default remains "Use TPoS items"; keep inventory info available without auto-enabling.
if (!this.formDialog.data.inventory_id) {
this.formDialog.data.inventory_id = data.inventory_id
this.formDialog.data.inventory_tags = [...data.tags]
this.formDialog.data.inventory_omit_tags = [...data.omit_tags]
}
} catch (error) {
console.error(error)
}
},
sendTposData() {
const data = {
...this.formDialog.data,
Expand All @@ -225,7 +289,16 @@ window.app = Vue.createApp({
tip_wallet:
(this.formDialog.advanced.tips && this.formDialog.data.tip_wallet) ||
'',
items: JSON.stringify(this.formDialog.data.items)
items: JSON.stringify(this.formDialog.data.items || [])
}
data.inventory_tags = data.inventory_tags || []
if (!this.inventoryStatus.enabled) {
data.use_inventory = false
} else if (!data.inventory_id) {
data.inventory_id = this.inventoryStatus.inventory_id
}
if (data.use_inventory && !data.inventory_id) {
data.use_inventory = false
}
// delete withdraw_between if value is empty string, defaults to 10 minutes
if (this.formDialog.data.withdraw_between == '') {
Expand Down Expand Up @@ -285,6 +358,64 @@ window.app = Vue.createApp({
LNbits.utils.notifyApiError(error)
})
},
saveInventorySettings(tpos) {
const wallet = _.findWhere(this.g.user.wallets, {id: tpos.wallet})
if (!wallet) return
const resolvedInventoryId =
this.inventoryStatus.inventory_id || tpos.inventory_id
const payload = {
use_inventory: this.inventoryStatus.enabled && tpos.use_inventory,
inventory_id:
this.inventoryStatus.enabled && tpos.use_inventory
? resolvedInventoryId
: null,
inventory_tags: tpos.inventory_tags || [],
inventory_omit_tags: tpos.inventory_omit_tags || []
}
if (payload.use_inventory && !payload.inventory_id) {
Quasar.Notify.create({
type: 'warning',
message: 'No inventory found for this user.'
})
return
}
LNbits.api
.request(
'PUT',
`/tpos/api/v1/tposs/${tpos.id}`,
wallet.adminkey,
payload
)
.then(response => {
this.tposs = _.reject(this.tposs, obj => obj.id == tpos.id)
this.tposs.push(mapTpos(response.data))
})
.catch(LNbits.utils.notifyApiError)
},
onInventoryModeChange(tpos, value) {
tpos.use_inventory = value
if (value && this.inventoryStatus.enabled) {
tpos.inventory_id = this.inventoryStatus.inventory_id
if (!tpos.inventory_tags.length && this.inventoryStatus.tags.length) {
tpos.inventory_tags = [...this.inventoryStatus.tags]
}
if (
!tpos.inventory_omit_tags.length &&
this.inventoryStatus.omit_tags
) {
tpos.inventory_omit_tags = [...this.inventoryStatus.omit_tags]
}
}
this.saveInventorySettings(tpos)
},
onInventoryTagsChange(tpos, tags) {
tpos.inventory_tags = tags || []
this.saveInventorySettings(tpos)
},
onInventoryOmitTagsChange(tpos, tags) {
tpos.inventory_omit_tags = tags || []
this.saveInventorySettings(tpos)
},
deleteTpos(tposId) {
const tpos = _.findWhere(this.tposs, {id: tposId})

Expand Down Expand Up @@ -506,6 +637,7 @@ window.app = Vue.createApp({
created() {
if (this.g.user.wallets.length) {
this.getTposs()
this.loadInventoryStatus()
}
LNbits.api
.request('GET', '/api/v1/currencies')
Expand Down
Loading