enhancement: add ability to set tax rate for individual items
This commit is contained in:
@@ -890,6 +890,8 @@
|
||||
"views.Settings.Help.telegram": "Telegram (we will not DM you)",
|
||||
"views.Settings.Help.email": "Email support",
|
||||
"views.Settings.POS.enableSquare": "Enable Square POS integration",
|
||||
"views.Settings.POS.taxPercentage.global.info": "Global tax rate applied to all products. Individual product tax rates will override this setting when specified.",
|
||||
"views.Settings.POS.taxPercentage.info": "Individual tax rate for this product. When set, this will override the global tax rate. Leave empty to use the global tax rate.",
|
||||
"views.Settings.POS.enablePos": "Enable POS integration",
|
||||
"views.Settings.POS.squareAccessToken": "Square Access token",
|
||||
"views.Settings.POS.squareLocationId": "Square Location ID",
|
||||
@@ -1238,6 +1240,8 @@
|
||||
"pos.views.Wallet.PosPane.fetchingRates": "Fetching exchange rates",
|
||||
"pos.views.Wallet.PosPane.uncategorized": "Uncategorized",
|
||||
"pos.views.Order.tax": "Tax",
|
||||
"pos.views.Order.taxFiat": "Tax (fiat)",
|
||||
"pos.views.Order.taxBitcoin": "Tax (Bitcoin)",
|
||||
"pos.views.Order.subtotalBitcoin": "Subtotal (Bitcoin)",
|
||||
"pos.views.Order.subtotalFiat": "Subtotal (fiat)",
|
||||
"pos.views.Order.addTip": "Add Tip",
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface LineItem {
|
||||
name: string;
|
||||
quantity: number;
|
||||
base_price_money: BasePriceMoney;
|
||||
taxPercentage?: string;
|
||||
}
|
||||
|
||||
export default class Order extends BaseModel {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ export default class Product extends BaseModel {
|
||||
@observable public price: string;
|
||||
@observable public category: string;
|
||||
@observable public status: ProductStatus;
|
||||
|
||||
@observable public taxPercentage?: string;
|
||||
@computed public get model(): string {
|
||||
return 'Product';
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export default class InventoryStore {
|
||||
found.pricedIn = newProduct.pricedIn;
|
||||
found.category = newProduct.category;
|
||||
found.status = newProduct.status;
|
||||
found.taxPercentage = newProduct.taxPercentage;
|
||||
} else {
|
||||
existingProducts.push(newProduct);
|
||||
}
|
||||
|
||||
+49
-10
@@ -190,21 +190,60 @@ export default class PosStore {
|
||||
this.currentOrder.total_money.amount = totalFiat.toNumber();
|
||||
this.currentOrder.total_money.sats = totalSats.toNumber();
|
||||
|
||||
// calculate taxes
|
||||
// calculate taxes using individual product rates when available
|
||||
const { settings } = this.settingsStore;
|
||||
const { taxPercentage } = settings.pos;
|
||||
|
||||
// Check if any line items have individual tax rates
|
||||
const hasIndividualTaxRates = this.currentOrder.line_items.some(
|
||||
(item) => item.taxPercentage && item.taxPercentage !== ''
|
||||
);
|
||||
|
||||
if (
|
||||
taxPercentage &&
|
||||
taxPercentage !== '0' &&
|
||||
taxPercentage !== ''
|
||||
hasIndividualTaxRates ||
|
||||
(taxPercentage && taxPercentage !== '0' && taxPercentage !== '')
|
||||
) {
|
||||
this.currentOrder.total_tax_money.amount = new BigNumber(
|
||||
totalFiat
|
||||
)
|
||||
.div(100)
|
||||
.multipliedBy(taxPercentage)
|
||||
.toNumber();
|
||||
let totalTaxFiat = new BigNumber(0);
|
||||
|
||||
if (hasIndividualTaxRates) {
|
||||
this.currentOrder.line_items.forEach((item) => {
|
||||
const itemTaxRate =
|
||||
item.taxPercentage && item.taxPercentage !== ''
|
||||
? item.taxPercentage
|
||||
: taxPercentage || '0';
|
||||
|
||||
let itemSubtotalFiat: BigNumber;
|
||||
|
||||
if (item.base_price_money.sats! > 0) {
|
||||
const satsAmount = new BigNumber(
|
||||
item.base_price_money.sats || 0
|
||||
).times(item.quantity);
|
||||
itemSubtotalFiat = new BigNumber(
|
||||
this.calcFiatAmountFromSats(
|
||||
satsAmount.toNumber()
|
||||
)
|
||||
).div(100);
|
||||
} else {
|
||||
itemSubtotalFiat = new BigNumber(
|
||||
item.base_price_money.amount || 0
|
||||
).times(item.quantity);
|
||||
}
|
||||
|
||||
const itemTaxFiat = itemSubtotalFiat
|
||||
.multipliedBy(new BigNumber(itemTaxRate))
|
||||
.dividedBy(100);
|
||||
|
||||
totalTaxFiat = totalTaxFiat.plus(itemTaxFiat);
|
||||
});
|
||||
} else {
|
||||
totalTaxFiat = totalFiat
|
||||
.div(100)
|
||||
.multipliedBy(Number(taxPercentage) || 0);
|
||||
}
|
||||
|
||||
this.currentOrder.total_tax_money.amount =
|
||||
totalTaxFiat.toNumber();
|
||||
|
||||
if (this.fiatStore.fiatRates) {
|
||||
const fiatEntry = this.fiatStore.fiatRates.filter(
|
||||
(entry: any) =>
|
||||
|
||||
+278
-60
@@ -24,9 +24,9 @@ import TextInput from '../components/TextInput';
|
||||
import { localeString } from '../utils/LocaleUtils';
|
||||
import { themeColor } from '../utils/ThemeUtils';
|
||||
import { SATS_PER_BTC } from '../utils/UnitsUtils';
|
||||
import { calculateTotalSats } from '../utils/TipUtils';
|
||||
|
||||
import BackendUtils from '../utils/BackendUtils';
|
||||
import { calculateTotalSats } from '../utils/TipUtils';
|
||||
import FiatStore from '../stores/FiatStore';
|
||||
import SettingsStore, { PosEnabled } from '../stores/SettingsStore';
|
||||
import UnitsStore from '../stores/UnitsStore';
|
||||
@@ -186,16 +186,77 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
.dividedBy(SATS_PER_BTC)
|
||||
.toFixed(2);
|
||||
|
||||
const taxSats = fiatEnabled // Use the defined fiatEnabled
|
||||
? new BigNumber(order.total_tax_money.amount)
|
||||
.div(100)
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.toFixed(0)
|
||||
: new BigNumber(subTotalSats)
|
||||
.multipliedBy(new BigNumber(taxPercentage || '0'))
|
||||
.dividedBy(100)
|
||||
.toFixed(0);
|
||||
// Calculate tax using individual product rates when available (for receipt)
|
||||
const calculateTaxSatsForReceipt = () => {
|
||||
const hasIndividualTaxRatesForReceipt = lineItems?.some(
|
||||
(item: any) => item.taxPercentage
|
||||
);
|
||||
|
||||
if (fiatEnabled && !hasIndividualTaxRatesForReceipt) {
|
||||
return new BigNumber(order.total_tax_money.amount)
|
||||
.div(100)
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.toFixed(0);
|
||||
}
|
||||
|
||||
// Check if any line items have individual tax rates
|
||||
const hasIndividualTaxRates = lineItems?.some(
|
||||
(item: any) => item.taxPercentage
|
||||
);
|
||||
|
||||
if (hasIndividualTaxRates) {
|
||||
let totalTaxSats = new BigNumber(0);
|
||||
lineItems?.forEach((item: any) => {
|
||||
// Use individual tax rate if set and not empty, otherwise use global rate
|
||||
const itemTaxRate =
|
||||
item.taxPercentage || taxPercentage || '0';
|
||||
|
||||
const validTaxRate = itemTaxRate || '0';
|
||||
|
||||
const fiatPriced = item.base_price_money.amount > 0;
|
||||
let itemSubtotalSats: string;
|
||||
|
||||
if (fiatPriced) {
|
||||
let fiatAmount = new BigNumber(
|
||||
item.base_price_money.amount
|
||||
).multipliedBy(item.quantity);
|
||||
if (settings.pos.posEnabled === PosEnabled.Square) {
|
||||
fiatAmount = fiatAmount.div(100);
|
||||
}
|
||||
itemSubtotalSats = fiatAmount
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
} else {
|
||||
itemSubtotalSats = new BigNumber(
|
||||
item.base_price_money.sats || 0
|
||||
)
|
||||
.multipliedBy(item.quantity)
|
||||
.toFixed(0);
|
||||
}
|
||||
|
||||
const itemTaxSats = new BigNumber(itemSubtotalSats)
|
||||
.multipliedBy(new BigNumber(validTaxRate))
|
||||
.dividedBy(100)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
|
||||
totalTaxSats = totalTaxSats.plus(itemTaxSats);
|
||||
});
|
||||
|
||||
return totalTaxSats.toFixed(0);
|
||||
} else {
|
||||
// Use global tax rate for all items
|
||||
return new BigNumber(subTotalSats)
|
||||
.multipliedBy(new BigNumber(taxPercentage || '0'))
|
||||
.dividedBy(100)
|
||||
.toFixed(0);
|
||||
}
|
||||
};
|
||||
|
||||
const taxSats = calculateTaxSatsForReceipt();
|
||||
|
||||
// sats
|
||||
// total amount is subtotal + tip + tax
|
||||
@@ -345,7 +406,6 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
'sats'
|
||||
);
|
||||
}
|
||||
|
||||
templateHtml += receiptHtmlRow(keyValue, displayValue);
|
||||
});
|
||||
|
||||
@@ -392,13 +452,55 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
);
|
||||
}
|
||||
|
||||
// Determine tax display label for receipt
|
||||
const getReceiptTaxLabel = () => {
|
||||
if (!lineItems || lineItems.length === 0) {
|
||||
return localeString('pos.views.Order.tax');
|
||||
}
|
||||
|
||||
// Get all unique tax rates used in the order
|
||||
const taxRates = new Set<string>();
|
||||
lineItems.forEach((item: any) => {
|
||||
// Use individual tax rate if set and not empty, otherwise use global rate
|
||||
const itemTaxRate = item.taxPercentage || taxPercentage || '0';
|
||||
|
||||
// Ensure valid rate for display
|
||||
const displayTaxRate = itemTaxRate || '0';
|
||||
taxRates.add(displayTaxRate);
|
||||
});
|
||||
|
||||
const uniqueRates = Array.from(taxRates);
|
||||
|
||||
if (uniqueRates.length === 1) {
|
||||
// All items have the same tax rate
|
||||
const rate = uniqueRates[0];
|
||||
if (rate === '0') {
|
||||
return localeString('pos.views.Order.tax');
|
||||
}
|
||||
return `${localeString('pos.views.Order.tax')} (${rate}%)`;
|
||||
} else {
|
||||
// Multiple different tax rates - show breakdown
|
||||
const rateList = uniqueRates
|
||||
.filter((rate) => rate !== '0')
|
||||
.map((rate) => `${rate}%`)
|
||||
.join(', ');
|
||||
|
||||
if (rateList === '') {
|
||||
return localeString('pos.views.Order.tax');
|
||||
}
|
||||
|
||||
return `${localeString('pos.views.Order.tax')} (${rateList})`;
|
||||
}
|
||||
};
|
||||
|
||||
templateHtml += receiptHtmlRow(
|
||||
`${localeString('pos.views.Order.tax')}${
|
||||
taxPercentage && Number(taxPercentage) > 0
|
||||
? ` (${taxPercentage}%)`
|
||||
: ''
|
||||
}`,
|
||||
order.getTaxMoneyDisplay
|
||||
getReceiptTaxLabel(),
|
||||
this.props.FiatStore.formatAmountForDisplay(
|
||||
new BigNumber(taxSats)
|
||||
.multipliedBy(rate)
|
||||
.dividedBy(SATS_PER_BTC)
|
||||
.toFixed(2)
|
||||
)
|
||||
);
|
||||
|
||||
templateHtml += receiptHtmlRow(
|
||||
@@ -500,15 +602,14 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
? `${merchantName} POS powered by ZEUS - Order ${order?.id}`
|
||||
: `ZEUS POS - Order ${order?.id}`;
|
||||
|
||||
// round to nearest sat
|
||||
let subTotalSats: string;
|
||||
if (settings.pos.posEnabled === PosEnabled.Square) {
|
||||
subTotalSats = new BigNumber(order?.total_money.amount)
|
||||
// subtract tax for subtotal if using Square
|
||||
.minus(order?.total_tax_money.amount)
|
||||
.div(100)
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
} else {
|
||||
subTotalSats =
|
||||
@@ -518,6 +619,7 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
.div(100)
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
}
|
||||
|
||||
@@ -526,16 +628,63 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
.dividedBy(SATS_PER_BTC)
|
||||
.toFixed(2);
|
||||
|
||||
const taxSats = fiatEnabled
|
||||
? new BigNumber(order?.total_tax_money.amount)
|
||||
.div(100)
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.toFixed(0)
|
||||
: new BigNumber(subTotalSats)
|
||||
.multipliedBy(new BigNumber(taxPercentage || '0'))
|
||||
.dividedBy(100)
|
||||
.toFixed(0);
|
||||
const calculateTaxSats = () => {
|
||||
const hasIndividualTaxRates = lineItems?.some(
|
||||
(item: any) => item.taxPercentage
|
||||
);
|
||||
|
||||
if (hasIndividualTaxRates) {
|
||||
let totalTaxSats = new BigNumber(0);
|
||||
lineItems?.forEach((item: any) => {
|
||||
const itemTaxRate =
|
||||
item.taxPercentage || taxPercentage || '0';
|
||||
|
||||
const validTaxRate = itemTaxRate || '0';
|
||||
|
||||
const fiatPriced = item.base_price_money.amount > 0;
|
||||
let itemSubtotalSats: string;
|
||||
|
||||
if (fiatPriced) {
|
||||
let fiatAmount = new BigNumber(
|
||||
item.base_price_money.amount
|
||||
).multipliedBy(item.quantity);
|
||||
// Only divide by 100 if using Square (amount is in cents)
|
||||
if (settings.pos.posEnabled === PosEnabled.Square) {
|
||||
fiatAmount = fiatAmount.div(100);
|
||||
}
|
||||
itemSubtotalSats = fiatAmount
|
||||
.div(rate)
|
||||
.multipliedBy(SATS_PER_BTC)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
} else {
|
||||
itemSubtotalSats = new BigNumber(
|
||||
item.base_price_money.sats || 0
|
||||
)
|
||||
.multipliedBy(item.quantity)
|
||||
.toFixed(0);
|
||||
}
|
||||
|
||||
const itemTaxSats = new BigNumber(itemSubtotalSats)
|
||||
.multipliedBy(new BigNumber(validTaxRate))
|
||||
.dividedBy(100)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
|
||||
totalTaxSats = totalTaxSats.plus(itemTaxSats);
|
||||
});
|
||||
|
||||
return totalTaxSats.toFixed(0);
|
||||
} else {
|
||||
return new BigNumber(subTotalSats)
|
||||
.multipliedBy(new BigNumber(taxPercentage || '0'))
|
||||
.dividedBy(100)
|
||||
.integerValue(BigNumber.ROUND_HALF_UP)
|
||||
.toFixed(0);
|
||||
}
|
||||
};
|
||||
|
||||
const taxSats = calculateTaxSats();
|
||||
|
||||
const twentyPercentButton = () => (
|
||||
<Text
|
||||
@@ -743,28 +892,72 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
: item.base_price_money.amount
|
||||
: item.base_price_money.sats;
|
||||
|
||||
const itemTaxRate = item.taxPercentage
|
||||
? ` + ${item.taxPercentage}% ${localeString(
|
||||
'pos.views.Order.tax'
|
||||
)}`
|
||||
: taxPercentage
|
||||
? ` + ${taxPercentage}% ${localeString(
|
||||
'pos.views.Order.tax'
|
||||
)}`
|
||||
: '';
|
||||
|
||||
let unitDisplayValue, totalDisplayValue;
|
||||
if (fiatPriced) {
|
||||
unitDisplayValue = UnitsStore.getFormattedAmount(
|
||||
new BigNumber(unitPrice).toFixed(2),
|
||||
'fiat'
|
||||
);
|
||||
totalDisplayValue = UnitsStore.getFormattedAmount(
|
||||
new BigNumber(unitPrice)
|
||||
.multipliedBy(item.quantity)
|
||||
.toFixed(2),
|
||||
'fiat'
|
||||
unitDisplayValue = FiatStore.formatAmountForDisplay(
|
||||
new BigNumber(unitPrice).toFixed(2)
|
||||
);
|
||||
totalDisplayValue =
|
||||
FiatStore.formatAmountForDisplay(
|
||||
new BigNumber(unitPrice)
|
||||
.multipliedBy(item.quantity)
|
||||
.toFixed(2)
|
||||
) + itemTaxRate;
|
||||
} else {
|
||||
unitDisplayValue = UnitsStore.getFormattedAmount(
|
||||
unitPrice,
|
||||
'sats'
|
||||
);
|
||||
totalDisplayValue = UnitsStore.getFormattedAmount(
|
||||
new BigNumber(unitPrice)
|
||||
.multipliedBy(item.quantity)
|
||||
.toString(),
|
||||
'sats'
|
||||
const baseDisplayValue =
|
||||
bitcoinUnits === 'sats' ? (
|
||||
<Amount
|
||||
fixedUnits="sats"
|
||||
sats={new BigNumber(unitPrice)
|
||||
.multipliedBy(item.quantity)
|
||||
.toString()}
|
||||
/>
|
||||
) : (
|
||||
<Amount
|
||||
fixedUnits="BTC"
|
||||
sats={new BigNumber(unitPrice)
|
||||
.multipliedBy(item.quantity)
|
||||
.toString()}
|
||||
/>
|
||||
);
|
||||
totalDisplayValue = (
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}}
|
||||
>
|
||||
{baseDisplayValue}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}}
|
||||
>
|
||||
{itemTaxRate}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1091,22 +1284,47 @@ export default class OrderView extends React.Component<OrderProps, OrderState> {
|
||||
</>
|
||||
)}
|
||||
|
||||
<KeyValue
|
||||
keyValue={`${localeString('pos.views.Order.tax')}${
|
||||
taxPercentage && Number(taxPercentage) > 0
|
||||
? ` (${taxPercentage}%)`
|
||||
: ''
|
||||
}`}
|
||||
value={
|
||||
fiatEnabled ? (
|
||||
order.getTaxMoneyDisplay
|
||||
) : bitcoinUnits === 'sats' ? (
|
||||
<Amount fixedUnits="sats" sats={taxSats} />
|
||||
) : (
|
||||
<Amount fixedUnits="BTC" sats={taxSats} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
{fiatEnabled && (
|
||||
<KeyValue
|
||||
keyValue={localeString('pos.views.Order.taxFiat')}
|
||||
value={FiatStore.formatAmountForDisplay(
|
||||
new BigNumber(taxSats)
|
||||
.multipliedBy(rate)
|
||||
.dividedBy(SATS_PER_BTC)
|
||||
.toFixed(2)
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.setState({
|
||||
bitcoinUnits:
|
||||
this.state.bitcoinUnits === 'sats'
|
||||
? 'BTC'
|
||||
: 'sats'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<KeyValue
|
||||
keyValue={
|
||||
lineItems?.some(
|
||||
(item: any) => item.taxPercentage
|
||||
)
|
||||
? localeString('pos.views.Order.taxBitcoin')
|
||||
: `${localeString(
|
||||
'pos.views.Order.tax'
|
||||
)} (${taxPercentage || '0'}%)`
|
||||
}
|
||||
value={
|
||||
bitcoinUnits === 'sats' ? (
|
||||
<Amount fixedUnits="sats" sats={taxSats} />
|
||||
) : (
|
||||
<Amount fixedUnits="BTC" sats={taxSats} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{fiatEnabled && (
|
||||
<KeyValue
|
||||
|
||||
@@ -22,6 +22,7 @@ import LoadingIndicator from '../../components/LoadingIndicator';
|
||||
import TextInput from '../../components/TextInput';
|
||||
import AmountInput from '../../components/AmountInput';
|
||||
import Switch from '../../components/Switch';
|
||||
import Text from '../../components/Text';
|
||||
|
||||
import { themeColor } from '../../utils/ThemeUtils';
|
||||
import { localeString } from '../../utils/LocaleUtils';
|
||||
@@ -108,7 +109,8 @@ export default class ProductDetails extends React.Component<
|
||||
pricedIn: PricedIn.Fiat,
|
||||
price: '',
|
||||
category: '',
|
||||
status: ProductStatus.Active
|
||||
status: ProductStatus.Active,
|
||||
taxPercentage: ''
|
||||
}),
|
||||
isLoading: false,
|
||||
isExisting: false
|
||||
@@ -124,20 +126,35 @@ export default class ProductDetails extends React.Component<
|
||||
|
||||
if (product) {
|
||||
if (this.props.UnitsStore.units !== product.pricedIn) {
|
||||
// change unit to match product
|
||||
while (
|
||||
this.props.UnitsStore.units !== product.pricedIn
|
||||
) {
|
||||
this.props.UnitsStore.changeUnits();
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
categories: categoryOptions,
|
||||
product,
|
||||
isLoading: false,
|
||||
isExisting: true
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
categories: categoryOptions,
|
||||
product: new Product({
|
||||
id: uuidv4(),
|
||||
name: '',
|
||||
sku: '',
|
||||
pricedIn: PricedIn.Fiat,
|
||||
price: '',
|
||||
category: '',
|
||||
status: ProductStatus.Active,
|
||||
taxPercentage: ''
|
||||
}),
|
||||
isLoading: false,
|
||||
isExisting: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -164,6 +181,16 @@ export default class ProductDetails extends React.Component<
|
||||
}
|
||||
value = value;
|
||||
break;
|
||||
|
||||
case 'taxPercentage':
|
||||
if (value.includes('-')) {
|
||||
return;
|
||||
}
|
||||
value = value.replace(',', '.');
|
||||
if (value !== '' && isNaN(parseFloat(value))) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
product[field] = value;
|
||||
this.setState({ product });
|
||||
@@ -350,6 +377,37 @@ export default class ProductDetails extends React.Component<
|
||||
this.setValue('price', price);
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: themeColor(
|
||||
'secondaryText'
|
||||
),
|
||||
fontFamily:
|
||||
'PPNeueMontreal-Book',
|
||||
marginBottom: 5
|
||||
}}
|
||||
infoModalText={localeString(
|
||||
'views.Settings.POS.taxPercentage.info'
|
||||
)}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.POS.taxPercentage'
|
||||
)}
|
||||
</Text>
|
||||
<TextInput
|
||||
placeholder="0"
|
||||
value={product?.taxPercentage}
|
||||
keyboardType="decimal-pad"
|
||||
onChangeText={(text: string) => {
|
||||
this.setValue(
|
||||
'taxPercentage',
|
||||
text
|
||||
);
|
||||
}}
|
||||
suffix="%"
|
||||
right={20}
|
||||
style={styles.textInput}
|
||||
/>
|
||||
<DropdownSetting
|
||||
title={localeString(
|
||||
'views.Settings.POS.Category.name'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, ScrollView, Text, View } from 'react-native';
|
||||
import { Platform, ScrollView, View } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '../../components/SuccessErrorMessage';
|
||||
import Screen from '../../components/Screen';
|
||||
import Switch from '../../components/Switch';
|
||||
import Text from '../../components/Text';
|
||||
import TextInput from '../../components/TextInput';
|
||||
|
||||
import SettingsStore, {
|
||||
@@ -518,6 +519,9 @@ export default class PointOfSale extends React.Component<
|
||||
color: themeColor('secondaryText'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}}
|
||||
infoModalText={localeString(
|
||||
'views.Settings.POS.taxPercentage.global.info'
|
||||
)}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.POS.taxPercentage'
|
||||
|
||||
@@ -307,12 +307,20 @@ export default class StandalonePosPane extends React.PureComponent<
|
||||
product.price.toString().replace(/,/g, '.')
|
||||
);
|
||||
|
||||
const item = order.line_items.find(
|
||||
(item) =>
|
||||
item.name === product.name &&
|
||||
(item.base_price_money.amount === productCalcPrice ||
|
||||
item.base_price_money.sats === productCalcPrice)
|
||||
);
|
||||
const item = order.line_items.find((item) => {
|
||||
const nameMatches = item.name === product.name;
|
||||
const taxMatches = item.taxPercentage === product.taxPercentage;
|
||||
|
||||
let priceMatches = false;
|
||||
if (product.pricedIn === PricedIn.Fiat) {
|
||||
priceMatches =
|
||||
item.base_price_money.amount === productCalcPrice;
|
||||
} else {
|
||||
priceMatches = item.base_price_money.sats === productCalcPrice;
|
||||
}
|
||||
|
||||
return nameMatches && priceMatches && taxMatches;
|
||||
});
|
||||
|
||||
if (item) {
|
||||
item.quantity++;
|
||||
@@ -320,6 +328,7 @@ export default class StandalonePosPane extends React.PureComponent<
|
||||
order.line_items.push({
|
||||
name: product.name,
|
||||
quantity: 1,
|
||||
taxPercentage: product.taxPercentage || '',
|
||||
base_price_money: {
|
||||
amount:
|
||||
product.pricedIn === PricedIn.Fiat
|
||||
|
||||
Reference in New Issue
Block a user