-
+
- {{ __('Total Available') }}:
+ {{ __("Total Available") }}:
{{ totalAvailable }} {{ displayUom }}
- {{ warehouses.length === 1
- ? __('in 1 warehouse')
- : __('in {0} warehouses', [warehouses.length])
+ {{
+ warehouses.length === 1
+ ? __("in 1 warehouse")
+ : __("in {0} warehouses", [warehouses.length])
}}
-
- ≈ {{ Math.floor(convertToBarcodeUom(totalAvailable) * 100) / 100 }} {{ selectedBarcodeUom }}
+
+ ≈ {{ Math.floor(convertToBarcodeUom(totalAvailable) * 100) / 100 }}
+ {{ selectedBarcodeUom }}
@@ -498,9 +852,9 @@
* RTL Support: Fully compatible with right-to-left languages
* Translations: All user-facing strings use __() for i18n
*/
-import { ref, computed, watch, nextTick } from 'vue'
-import { call, Dialog } from 'frappe-ui'
-import { __ } from '@/utils/translation'
+import { ref, computed, watch, nextTick } from "vue";
+import { call, Dialog } from "frappe-ui";
+import { __ } from "@/utils/translation";
const props = defineProps({
modelValue: Boolean,
@@ -509,345 +863,354 @@ const props = defineProps({
itemName: String,
uom: {
type: String,
- default: 'Nos'
+ default: "Nos",
},
company: String,
currency: {
type: String,
- default: ''
+ default: "",
},
// For search mode
posProfile: String,
// Mode: 'item' for single item (default), 'search' for general search
mode: {
type: String,
- default: 'item'
- }
-})
+ default: "item",
+ },
+});
-const emit = defineEmits(['update:modelValue', 'close'])
+const emit = defineEmits(["update:modelValue", "close"]);
// v-model binding for Dialog
const show = computed({
get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val)
-})
+ set: (val) => emit("update:modelValue", val),
+});
// Close dialog helper
function closeDialog() {
- emit('update:modelValue', false)
- emit('close')
+ emit("update:modelValue", false);
+ emit("close");
}
// Determine if we're in search mode
-const isSearchMode = computed(() => props.mode === 'search')
+const isSearchMode = computed(() => props.mode === "search");
// Search state
-const searchQuery = ref('')
-const searchResults = ref([])
-const searching = ref(false)
-const selectedResultIndex = ref(-1)
-const showSearchResults = ref(false)
-const searchInputRef = ref(null)
-const resultRefs = ref([])
-let searchDebounce = null
+const searchQuery = ref("");
+const searchResults = ref([]);
+const searching = ref(false);
+const selectedResultIndex = ref(-1);
+const showSearchResults = ref(false);
+const searchInputRef = ref(null);
+const resultRefs = ref([]);
+let searchDebounce = null;
// Selected item state (for search mode)
-const selectedItemCode = ref('')
-const selectedItemName = ref('')
-const selectedItemImage = ref('')
-const selectedUom = ref('Nos')
-const selectedItemHasVariants = ref(false)
-const selectedBarcodeUom = ref('') // Single barcode UOM if different from default
-const selectedItemUoms = ref([]) // Conversion factors for UOMs
+const selectedItemCode = ref("");
+const selectedItemName = ref("");
+const selectedItemImage = ref("");
+const selectedUom = ref("Nos");
+const selectedItemHasVariants = ref(false);
+const selectedBarcodeUom = ref(""); // Single barcode UOM if different from default
+const selectedItemUoms = ref([]); // Conversion factors for UOMs
// Variant state
-const variants = ref([])
-const selectedVariants = ref([])
-const loadingVariants = ref(false)
-const showVariantSelection = ref(false)
+const variants = ref([]);
+const selectedVariants = ref([]);
+const loadingVariants = ref(false);
+const showVariantSelection = ref(false);
// Warehouse availability state
-const loading = ref(false)
-const error = ref(null)
-const warehouses = ref([])
-const isReady = ref(false) // Gate to prevent showing content before data is ready
+const loading = ref(false);
+const error = ref(null);
+const warehouses = ref([]);
+const isReady = ref(false); // Gate to prevent showing content before data is ready
// Computed display values
const displayItemName = computed(() => {
if (isSearchMode.value) {
- return selectedItemName.value || ''
+ return selectedItemName.value || "";
}
- return props.itemName || props.itemCode || ''
-})
+ return props.itemName || props.itemCode || "";
+});
const displayUom = computed(() => {
if (isSearchMode.value) {
- return selectedUom.value || 'Nos'
+ return selectedUom.value || "Nos";
}
- return props.uom || 'Nos'
-})
+ return props.uom || "Nos";
+});
const totalAvailable = computed(() => {
- if (!warehouses.value || warehouses.value.length === 0) return 0
- return Math.floor(warehouses.value.reduce((sum, w) => sum + (w.available_qty || 0), 0))
-})
+ if (!warehouses.value || warehouses.value.length === 0) return 0;
+ return Math.floor(warehouses.value.reduce((sum, w) => sum + (w.available_qty || 0), 0));
+});
// Group warehouses by warehouse name when multiple variants selected
const groupedWarehouses = computed(() => {
- if (selectedVariants.value.length <= 1) return {}
-
- const grouped = {}
+ if (selectedVariants.value.length <= 1) return {};
+
+ const grouped = {};
for (const warehouse of warehouses.value) {
- const key = warehouse.warehouse || warehouse.warehouse_name
+ const key = warehouse.warehouse || warehouse.warehouse_name;
if (!grouped[key]) {
- grouped[key] = []
+ grouped[key] = [];
}
- grouped[key].push(warehouse)
+ grouped[key].push(warehouse);
}
- return grouped
-})
+ return grouped;
+});
// Helper functions for variant info
function getVariantName(itemCode) {
- const variant = variants.value.find(v => v.item_code === itemCode)
- return variant ? variant.item_name : itemCode
+ const variant = variants.value.find((v) => v.item_code === itemCode);
+ return variant ? variant.item_name : itemCode;
}
function getVariantUom(itemCode) {
- const variant = variants.value.find(v => v.item_code === itemCode)
- return variant ? (variant.stock_uom || 'Nos') : displayUom.value
+ const variant = variants.value.find((v) => v.item_code === itemCode);
+ return variant ? variant.stock_uom || "Nos" : displayUom.value;
}
// Initialize and load based on mode
-watch(() => props.modelValue, async (newVal) => {
- if (newVal) {
- // Set loading immediately to prevent flash of empty content
- loading.value = true
- isReady.value = false
- error.value = null
-
- if (isSearchMode.value) {
- // Search mode - clear state and focus search
- resetSearchState()
- isReady.value = true
- loading.value = false
- await nextTick()
- focusSearch()
- } else if (props.itemCode) {
- // Item mode - check if item has variants first
- // We need to fetch item details to check has_variants
- try {
- const itemResponse = await call('pos_next.api.items.get_items', {
- pos_profile: props.posProfile,
- search_term: props.itemCode,
- start: 0,
- limit: 1
- })
- const item = itemResponse?.[0]
- if (item && item.has_variants) {
- selectedItemCode.value = props.itemCode
- selectedItemName.value = props.itemName || props.itemCode
- selectedItemHasVariants.value = true
- isReady.value = true
- await loadVariants()
- } else {
- // No variants, directly load availability
- isReady.value = true
- await loadAvailability()
+watch(
+ () => props.modelValue,
+ async (newVal) => {
+ if (newVal) {
+ // Set loading immediately to prevent flash of empty content
+ loading.value = true;
+ isReady.value = false;
+ error.value = null;
+
+ if (isSearchMode.value) {
+ // Search mode - clear state and focus search
+ resetSearchState();
+ isReady.value = true;
+ loading.value = false;
+ await nextTick();
+ focusSearch();
+ } else if (props.itemCode) {
+ // Item mode - check if item has variants first
+ // We need to fetch item details to check has_variants
+ try {
+ const itemResponse = await call("pos_next.api.items.get_items", {
+ pos_profile: props.posProfile,
+ search_term: props.itemCode,
+ start: 0,
+ limit: 1,
+ });
+ const item = itemResponse?.[0];
+ if (item && item.has_variants) {
+ selectedItemCode.value = props.itemCode;
+ selectedItemName.value = props.itemName || props.itemCode;
+ selectedItemHasVariants.value = true;
+ isReady.value = true;
+ await loadVariants();
+ } else {
+ // No variants, directly load availability
+ isReady.value = true;
+ await loadAvailability();
+ }
+ } catch (err) {
+ console.error("Error checking item variants:", err);
+ // Fallback to direct load
+ isReady.value = true;
+ await loadAvailability();
}
- } catch (err) {
- console.error('Error checking item variants:', err)
- // Fallback to direct load
- isReady.value = true
- await loadAvailability()
+ } else {
+ // No item code provided
+ isReady.value = true;
+ loading.value = false;
}
} else {
- // No item code provided
- isReady.value = true
- loading.value = false
+ // Reset when dialog closes
+ resetSearchState();
+ warehouses.value = [];
+ error.value = null;
+ isReady.value = false;
}
- } else {
- // Reset when dialog closes
- resetSearchState()
- warehouses.value = []
- error.value = null
- isReady.value = false
- }
-}, { immediate: true })
+ },
+ { immediate: true }
+);
function resetSearchState() {
- searchQuery.value = ''
- searchResults.value = []
- selectedResultIndex.value = -1
- showSearchResults.value = false
- selectedItemCode.value = ''
- selectedItemName.value = ''
- selectedItemImage.value = ''
- selectedUom.value = 'Nos'
- selectedItemHasVariants.value = false
- selectedBarcodeUom.value = ''
- selectedItemUoms.value = []
- variants.value = []
- selectedVariants.value = []
- showVariantSelection.value = false
- warehouses.value = []
- error.value = null
+ searchQuery.value = "";
+ searchResults.value = [];
+ selectedResultIndex.value = -1;
+ showSearchResults.value = false;
+ selectedItemCode.value = "";
+ selectedItemName.value = "";
+ selectedItemImage.value = "";
+ selectedUom.value = "Nos";
+ selectedItemHasVariants.value = false;
+ selectedBarcodeUom.value = "";
+ selectedItemUoms.value = [];
+ variants.value = [];
+ selectedVariants.value = [];
+ showVariantSelection.value = false;
+ warehouses.value = [];
+ error.value = null;
// Don't reset isReady here - it's managed by the watch
}
function focusSearch() {
if (searchInputRef.value) {
- searchInputRef.value.focus()
+ searchInputRef.value.focus();
}
}
async function handleSearchInput() {
- selectedResultIndex.value = -1
- showSearchResults.value = true
+ selectedResultIndex.value = -1;
+ showSearchResults.value = true;
if (searchDebounce) {
- clearTimeout(searchDebounce)
+ clearTimeout(searchDebounce);
}
// Start searching after just 1 character for faster autocomplete
if (searchQuery.value.length < 1) {
- searchResults.value = []
- searching.value = false
- return
+ searchResults.value = [];
+ searching.value = false;
+ return;
}
- searching.value = true
+ searching.value = true;
// Shorter debounce for snappier autocomplete (150ms)
searchDebounce = setTimeout(async () => {
- await performSearch()
- }, 150)
+ await performSearch();
+ }, 150);
}
async function performSearch() {
if (searchQuery.value.length < 1) {
- searching.value = false
- return
+ searching.value = false;
+ return;
}
try {
- const response = await call('pos_next.api.items.get_items', {
+ const response = await call("pos_next.api.items.get_items", {
pos_profile: props.posProfile,
search_term: searchQuery.value,
start: 0,
- limit: 15 // Show more results for better autocomplete
- })
+ limit: 15, // Show more results for better autocomplete
+ });
- searchResults.value = response || []
+ searchResults.value = response || [];
// Auto-select first result for quick enter
if (searchResults.value.length > 0) {
- selectedResultIndex.value = 0
+ selectedResultIndex.value = 0;
}
} catch (err) {
- console.error('Error searching items:', err)
- searchResults.value = []
+ console.error("Error searching items:", err);
+ searchResults.value = [];
} finally {
- searching.value = false
+ searching.value = false;
}
}
function navigateResults(direction) {
- if (searchResults.value.length === 0) return
+ if (searchResults.value.length === 0) return;
- const newIndex = selectedResultIndex.value + direction
+ const newIndex = selectedResultIndex.value + direction;
if (newIndex >= 0 && newIndex < searchResults.value.length) {
- selectedResultIndex.value = newIndex
+ selectedResultIndex.value = newIndex;
// Scroll selected item into view
nextTick(() => {
- const resultElements = document.querySelectorAll('[ref="resultRefs"]')
+ const resultElements = document.querySelectorAll('[ref="resultRefs"]');
if (resultElements[newIndex]) {
- resultElements[newIndex].scrollIntoView({ block: 'nearest' })
+ resultElements[newIndex].scrollIntoView({ block: "nearest" });
}
- })
+ });
}
}
function selectFirstResult() {
if (searchResults.value.length > 0) {
- const index = selectedResultIndex.value >= 0 ? selectedResultIndex.value : 0
- selectItem(searchResults.value[index])
+ const index = selectedResultIndex.value >= 0 ? selectedResultIndex.value : 0;
+ selectItem(searchResults.value[index]);
}
}
async function selectItem(item) {
// Set loading state before clearing search results
- loading.value = true
+ loading.value = true;
- selectedItemCode.value = item.item_code
- selectedItemName.value = item.item_name
- selectedItemImage.value = item.image || ''
- selectedUom.value = item.stock_uom || item.uom || 'Nos'
- selectedItemHasVariants.value = item.has_variants || false
- selectedItemUoms.value = item.item_uoms || []
+ selectedItemCode.value = item.item_code;
+ selectedItemName.value = item.item_name;
+ selectedItemImage.value = item.image || "";
+ selectedUom.value = item.stock_uom || item.uom || "Nos";
+ selectedItemHasVariants.value = item.has_variants || false;
+ selectedItemUoms.value = item.item_uoms || [];
// Check if barcode_uoms has a single value different from default UOM
- const barcodeUoms = item.barcode_uoms ? item.barcode_uoms.split(',').map(u => u.trim()).filter(u => u) : []
+ const barcodeUoms = item.barcode_uoms
+ ? item.barcode_uoms
+ .split(",")
+ .map((u) => u.trim())
+ .filter((u) => u)
+ : [];
if (barcodeUoms.length === 1 && barcodeUoms[0] !== selectedUom.value) {
- selectedBarcodeUom.value = barcodeUoms[0]
+ selectedBarcodeUom.value = barcodeUoms[0];
} else {
- selectedBarcodeUom.value = ''
+ selectedBarcodeUom.value = "";
}
- searchQuery.value = ''
- searchResults.value = []
- showSearchResults.value = false
- selectedResultIndex.value = -1
+ searchQuery.value = "";
+ searchResults.value = [];
+ showSearchResults.value = false;
+ selectedResultIndex.value = -1;
// Check if item has variants
if (selectedItemHasVariants.value) {
- await loadVariants()
+ await loadVariants();
} else {
// No variants, directly load availability
- await loadAvailability()
+ await loadAvailability();
}
}
function handleEscape() {
if (showSearchResults.value && searchResults.value.length > 0) {
// First escape closes dropdown
- showSearchResults.value = false
+ showSearchResults.value = false;
} else if (searchQuery.value) {
// Second escape clears search
- clearSearch()
+ clearSearch();
} else {
// Third escape closes dialog
- closeDialog()
+ closeDialog();
}
}
function clearSearch() {
- searchQuery.value = ''
- searchResults.value = []
- selectedResultIndex.value = -1
- showSearchResults.value = false
- focusSearch()
+ searchQuery.value = "";
+ searchResults.value = [];
+ selectedResultIndex.value = -1;
+ showSearchResults.value = false;
+ focusSearch();
}
function clearSelectedItem() {
- selectedItemCode.value = ''
- selectedItemName.value = ''
- selectedItemImage.value = ''
- selectedUom.value = 'Nos'
- selectedItemHasVariants.value = false
- selectedBarcodeUom.value = ''
- selectedItemUoms.value = []
- variants.value = []
- selectedVariants.value = []
- showVariantSelection.value = false
- warehouses.value = []
- error.value = null
+ selectedItemCode.value = "";
+ selectedItemName.value = "";
+ selectedItemImage.value = "";
+ selectedUom.value = "Nos";
+ selectedItemHasVariants.value = false;
+ selectedBarcodeUom.value = "";
+ selectedItemUoms.value = [];
+ variants.value = [];
+ selectedVariants.value = [];
+ showVariantSelection.value = false;
+ warehouses.value = [];
+ error.value = null;
nextTick(() => {
- focusSearch()
- })
+ focusSearch();
+ });
}
/**
@@ -856,108 +1219,108 @@ function clearSelectedItem() {
* @returns {number|null} Converted quantity or null if conversion not possible
*/
function convertToBarcodeUom(qty) {
- if (!selectedBarcodeUom.value || !selectedItemUoms.value.length) return null
+ if (!selectedBarcodeUom.value || !selectedItemUoms.value.length) return null;
- const uomData = selectedItemUoms.value.find(u => u.uom === selectedBarcodeUom.value)
- if (!uomData || !uomData.conversion_factor) return null
+ const uomData = selectedItemUoms.value.find((u) => u.uom === selectedBarcodeUom.value);
+ if (!uomData || !uomData.conversion_factor) return null;
// Convert: stock_qty / conversion_factor = barcode_uom_qty
- return qty / uomData.conversion_factor
+ return qty / uomData.conversion_factor;
}
async function loadVariants() {
- const templateItem = isSearchMode.value ? selectedItemCode.value : props.itemCode
- if (!templateItem || !props.posProfile) return
+ const templateItem = isSearchMode.value ? selectedItemCode.value : props.itemCode;
+ if (!templateItem || !props.posProfile) return;
- loadingVariants.value = true
- variants.value = []
- selectedVariants.value = []
- showVariantSelection.value = true
- error.value = null
+ loadingVariants.value = true;
+ variants.value = [];
+ selectedVariants.value = [];
+ showVariantSelection.value = true;
+ error.value = null;
try {
- const response = await call('pos_next.api.items.get_item_variants', {
+ const response = await call("pos_next.api.items.get_item_variants", {
template_item: templateItem,
- pos_profile: props.posProfile
- })
+ pos_profile: props.posProfile,
+ });
+
+ variants.value = response || [];
- variants.value = response || []
-
// If no variants found, load availability for the template item itself
if (variants.value.length === 0) {
- showVariantSelection.value = false
- loadAvailability()
+ showVariantSelection.value = false;
+ loadAvailability();
}
} catch (err) {
- console.error('Error loading variants:', err)
- error.value = err.message || __('Failed to load variants')
- showVariantSelection.value = false
+ console.error("Error loading variants:", err);
+ error.value = err.message || __("Failed to load variants");
+ showVariantSelection.value = false;
} finally {
- loadingVariants.value = false
+ loadingVariants.value = false;
}
}
function toggleVariantSelection(variant) {
- const index = selectedVariants.value.findIndex(v => v.item_code === variant.item_code)
+ const index = selectedVariants.value.findIndex((v) => v.item_code === variant.item_code);
if (index >= 0) {
- selectedVariants.value.splice(index, 1)
+ selectedVariants.value.splice(index, 1);
} else {
- selectedVariants.value.push(variant)
+ selectedVariants.value.push(variant);
}
}
function isVariantSelected(variant) {
- return selectedVariants.value.some(v => v.item_code === variant.item_code)
+ return selectedVariants.value.some((v) => v.item_code === variant.item_code);
}
function confirmVariantSelection() {
if (selectedVariants.value.length === 0) {
- error.value = __('Please select at least one variant')
- return
+ error.value = __("Please select at least one variant");
+ return;
}
- showVariantSelection.value = false
- loadAvailability()
+ showVariantSelection.value = false;
+ loadAvailability();
}
function selectAllVariants() {
- selectedVariants.value = [...variants.value]
+ selectedVariants.value = [...variants.value];
}
function deselectAllVariants() {
- selectedVariants.value = []
+ selectedVariants.value = [];
}
async function loadAvailability() {
- const targetItemCode = isSearchMode.value ? selectedItemCode.value : props.itemCode
+ const targetItemCode = isSearchMode.value ? selectedItemCode.value : props.itemCode;
- if (!targetItemCode) return
+ if (!targetItemCode) return;
- loading.value = true
- error.value = null
- warehouses.value = []
+ loading.value = true;
+ error.value = null;
+ warehouses.value = [];
try {
// If variants are selected, use item_codes parameter
if (selectedVariants.value.length > 0) {
- const itemCodes = selectedVariants.value.map(v => v.item_code)
- const response = await call('pos_next.api.items.get_item_warehouse_availability', {
+ const itemCodes = selectedVariants.value.map((v) => v.item_code);
+ const response = await call("pos_next.api.items.get_item_warehouse_availability", {
item_codes: JSON.stringify(itemCodes),
- company: props.company
- })
- warehouses.value = response || []
+ company: props.company,
+ });
+ warehouses.value = response || [];
} else {
// Single item (backward compatible)
- const response = await call('pos_next.api.items.get_item_warehouse_availability', {
+ const response = await call("pos_next.api.items.get_item_warehouse_availability", {
item_code: targetItemCode,
- company: props.company
- })
- warehouses.value = response || []
+ company: props.company,
+ });
+ warehouses.value = response || [];
}
} catch (err) {
- console.error('Error loading warehouse availability:', err)
- error.value = err.message || __('Failed to load warehouse availability')
+ console.error("Error loading warehouse availability:", err);
+ error.value = err.message || __("Failed to load warehouse availability");
} finally {
- loading.value = false
+ loading.value = false;
}
}
@@ -968,11 +1331,14 @@ async function loadAvailability() {
* @returns {string} HTML with highlighted matches
*/
function highlightMatch(text, query) {
- if (!text || !query) return text
-
- const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- const regex = new RegExp(`(${escapedQuery})`, 'gi')
- return text.replace(regex, '
$1')
+ if (!text || !query) return text;
+
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const regex = new RegExp(`(${escapedQuery})`, "gi");
+ return text.replace(
+ regex,
+ '
$1'
+ );
}
/**
@@ -981,10 +1347,10 @@ function highlightMatch(text, query) {
* @returns {string} Formatted price
*/
function formatPrice(price) {
- if (!price) return ''
- const num = Number(price)
- if (isNaN(num)) return ''
- return num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
+ if (!price) return "";
+ const num = Number(price);
+ if (isNaN(num)) return "";
+ return num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
diff --git a/POS/src/components/settings/CheckboxField.vue b/POS/src/components/settings/CheckboxField.vue
index 45e407efb..2720a8931 100644
--- a/POS/src/components/settings/CheckboxField.vue
+++ b/POS/src/components/settings/CheckboxField.vue
@@ -21,7 +21,7 @@
diff --git a/POS/src/components/settings/NumberField.vue b/POS/src/components/settings/NumberField.vue
index 7b2ab4581..3b5f603d9 100644
--- a/POS/src/components/settings/NumberField.vue
+++ b/POS/src/components/settings/NumberField.vue
@@ -20,7 +20,7 @@
diff --git a/POS/src/components/settings/POSSettings.vue b/POS/src/components/settings/POSSettings.vue
index 89ca3e85e..c28c2d258 100644
--- a/POS/src/components/settings/POSSettings.vue
+++ b/POS/src/components/settings/POSSettings.vue
@@ -8,21 +8,52 @@
>
-
+
-
+
-
{{ __('POS Settings') }}
+
+ {{ __("POS Settings") }}
+
-
@@ -36,11 +67,21 @@
size="sm"
>
-
-
+
+
- {{ __('Refresh') }}
+ {{ __("Refresh") }}
@@ -69,49 +130,102 @@
-
-
-
{{ __('Loading settings...') }}
+
+
+
+ {{ __("Loading settings...") }}
+
-
+
-
+
-
{{ __('Stock Management') }}
-
{{ __('Configure warehouse and inventory settings') }}
+
+ {{ __("Stock Management") }}
+
+
+ {{
+ __(
+ "Configure warehouse and inventory settings"
+ )
+ }}
+
-
-
+
+
- {{ __('Stock Controls') }}
+ {{
+ __("Stock Controls")
+ }}
@@ -119,49 +233,110 @@
-
-
+
+
- {{ __('Warehouse Selection') }}
+
+ {{ __("Warehouse Selection") }}
+
-
-
-
+
+
+
-
{{ __('Loading warehouses...') }}
+
+ {{ __("Loading warehouses...") }}
+
-
-
+
+
- {{ __('Stock Validation Policy') }}
+
+ {{ __("Stock Validation Policy") }}
+
@@ -171,17 +346,43 @@
-
-
+
+
- {{ __('Background Stock Sync') }}
-
-
-
{{ __('Active') }}
+
+ {{ __("Background Stock Sync") }}
+
+
+
+
{{
+ __("Active")
+ }}
-
-
-
{{ __('Inactive') }}
+
+
+
{{
+ __("Inactive")
+ }}
@@ -190,63 +391,147 @@
-
+
-
+
-
+
-
-
+
+
-
{{ __('Network Usage:') }}
-
{{ __('~15 KB per sync cycle') }}
-
{{ __('~{0} MB per hour', [Math.round((3600 / stockSyncIntervalSeconds) * 15 / 1024)]) }}
+
+ {{ __("Network Usage:") }}
+
+
+ {{ __("~15 KB per sync cycle") }}
+
+
+ {{
+ __("~{0} MB per hour", [
+ Math.round(
+ ((3600 /
+ stockSyncIntervalSeconds) *
+ 15) /
+ 1024
+ ),
+ ])
+ }}
+
@@ -257,25 +542,58 @@
-
+
-
{{ __('Sales Management') }}
-
{{ __('Configure pricing, discounts, and sales operations') }}
+
+ {{ __("Sales Management") }}
+
+
+ {{
+ __(
+ "Configure pricing, discounts, and sales operations"
+ )
+ }}
+
-
-
+
+
- {{ __('Sales Controls') }}
+ {{
+ __("Sales Controls")
+ }}
@@ -283,16 +601,32 @@
-
-
+
+
- {{ __('Pricing & Discounts') }}
+
+ {{ __("Pricing & Discounts") }}
+
@@ -332,10 +676,22 @@
-
-
+
+
- {{ __('Sales Operations') }}
+
+ {{ __("Sales Operations") }}
+
-
+
- {{ qzConnecting ? __('Connecting to QZ Tray...') : qzConnected ? __('QZ Tray Connected') : __('QZ Tray Not Connected') }}
+ {{
+ qzConnecting
+ ? __("Connecting to QZ Tray...")
+ : qzConnected
+ ? __("QZ Tray Connected")
+ : __("QZ Tray Not Connected")
+ }}
@@ -394,7 +777,14 @@
v-model="selectedPrinter"
:label="__('Printer')"
:options="printerOptions"
- :description="qzPrinters.length === 0 && !loadingPrinters ? __('No printers found. Is QZ Tray running?') : ''"
+ :description="
+ qzPrinters.length === 0 &&
+ !loadingPrinters
+ ? __(
+ 'No printers found. Is QZ Tray running?'
+ )
+ : ''
+ "
/>
@@ -420,49 +821,108 @@
qzCertStatus === 'trusted'
? 'bg-green-50 border-green-200'
: qzCertStatus === 'untrusted'
- ? 'bg-red-50 border-red-200'
- : 'bg-amber-50 border-amber-200'
+ ? 'bg-red-50 border-red-200'
+ : 'bg-amber-50 border-amber-200',
]"
>
-
-
+
+
-
+
- {{ __('Silent Print Certificate') }}
+ {{
+ __(
+ "Silent Print Certificate"
+ )
+ }}
-
- {{ __('Installed') }}
+
+ {{
+ __("Installed")
+ }}
-
- {{ __('Not Installed') }}
+
+ {{
+ __("Not Installed")
+ }}
-
- {{ __('Checking...') }}
+
+ {{
+ __("Checking...")
+ }}
@@ -471,70 +931,173 @@
v-if="qzCertStatus === 'trusted'"
class="text-xs text-green-800 leading-relaxed mb-2"
>
- {{ __('Certificate is installed and signing is active. Print jobs will be sent silently without confirmation dialogs.') }}
+ {{
+ __(
+ "Certificate is installed and signing is active. Print jobs will be sent silently without confirmation dialogs."
+ )
+ }}
- {{ __('Certificate is not installed on this machine. Generate a certificate, download it, and import it into QZ Tray.') }}
+ {{
+ __(
+ "Certificate is not installed on this machine. Generate a certificate, download it, and import it into QZ Tray."
+ )
+ }}
- {{ __('To print without confirmation dialogs, generate a signing certificate and install it on each POS machine.') }}
+ {{
+ __(
+ "To print without confirmation dialogs, generate a signing certificate and install it on each POS machine."
+ )
+ }}
-
+
-
- {{ __('Download the certificate and import it into QZ Tray, then restart QZ Tray.') }}
+ {{
+ __(
+ "Download the certificate and import it into QZ Tray, then restart QZ Tray."
+ )
+ }}
-
+
-
-
+
+
-
- {{ __('QZ Tray must be installed and running on this computer. Download from') }}
- qz.io.
- {{ __('If QZ Tray is unavailable, printing will fall back to the browser dialog.') }}
+
+ {{
+ __(
+ "QZ Tray must be installed and running on this computer. Download from"
+ )
+ }}
+ qz.io.
+ {{
+ __(
+ "If QZ Tray is unavailable, printing will fall back to the browser dialog."
+ )
+ }}
@@ -543,17 +1106,38 @@
-
-
-
-
-
+
+
+
+
-
{{ __('No POS Profile Selected') }}
-
{{ __('Please select a POS Profile to configure settings') }}
+
+ {{ __("No POS Profile Selected") }}
+
+
+ {{ __("Please select a POS Profile to configure settings") }}
+
@@ -563,43 +1147,39 @@
${html}
-