mirror of
https://github.com/frappe/books.git
synced 2025-01-09 09:50:27 +00:00
feat: WeightEnabledBarcode in POS
This commit is contained in:
parent
364173101b
commit
896b16e08e
198
src/components/Controls/weightEnabledBarcode.vue
Normal file
198
src/components/Controls/weightEnabledBarcode.vue
Normal file
@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
px-2
|
||||
w-36
|
||||
flex
|
||||
items-center
|
||||
border
|
||||
rounded
|
||||
bg-gray-50
|
||||
dark:border-gray-800 dark:bg-gray-890 dark:focus-within:bg-gray-900
|
||||
focus-within:bg-gray-100
|
||||
"
|
||||
>
|
||||
<input
|
||||
ref="scanner"
|
||||
type="text"
|
||||
class="text-base placeholder-gray-600 w-full bg-transparent outline-none"
|
||||
:placeholder="t`Enter weight barcode`"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<feather-icon
|
||||
name="maximize"
|
||||
class="w-3 h-3 text-gray-600 dark:text-gray-400 cursor-text"
|
||||
@click="() => ($refs.scanner as HTMLInputElement).focus()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
emits: ['item-selected'],
|
||||
data() {
|
||||
return {
|
||||
timerId: null,
|
||||
barcode: '',
|
||||
cooldown: '',
|
||||
} as {
|
||||
timerId: null | ReturnType<typeof setTimeout>;
|
||||
barcode: string;
|
||||
cooldown: string;
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.scanListener);
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener('keydown', this.scanListener);
|
||||
},
|
||||
activated() {
|
||||
document.addEventListener('keydown', this.scanListener);
|
||||
},
|
||||
deactivated() {
|
||||
document.removeEventListener('keydown', this.scanListener);
|
||||
},
|
||||
methods: {
|
||||
handleChange(e: Event) {
|
||||
const elem = e.target as HTMLInputElement;
|
||||
this.selectItem(elem.value);
|
||||
elem.value = '';
|
||||
},
|
||||
|
||||
async selectItem(code: string) {
|
||||
const barcode = code.trim();
|
||||
if (this.cooldown === barcode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cooldown = barcode;
|
||||
setTimeout(() => (this.cooldown = ''), 100);
|
||||
|
||||
const isWeightEnabled =
|
||||
this.fyo.singles.POSSettings?.weightEnabledBarcode;
|
||||
const randomNumberLength = this.fyo.singles.POSSettings
|
||||
?.randomNumberLength as number;
|
||||
const ItemBarcodeLength = this.fyo.singles.POSSettings
|
||||
?.ItemBarcodeLength as number;
|
||||
const quantityBarcodeLength = this.fyo.singles.POSSettings
|
||||
?.quantityBarcodeLength as number;
|
||||
|
||||
if (
|
||||
code.length !==
|
||||
randomNumberLength + ItemBarcodeLength + quantityBarcodeLength
|
||||
) {
|
||||
return this.error(this.t`Barcode ${barcode} has an invalid length.`);
|
||||
}
|
||||
|
||||
const filters: Record<string, string> = isWeightEnabled
|
||||
? {
|
||||
weightBarcode: barcode.slice(
|
||||
randomNumberLength,
|
||||
randomNumberLength + ItemBarcodeLength
|
||||
),
|
||||
}
|
||||
: { barcode };
|
||||
const fields = isWeightEnabled ? ['name', 'unit'] : ['name'];
|
||||
|
||||
const items =
|
||||
(await this.fyo.db.getAll('Item', { filters, fields })) || [];
|
||||
const { name, unit } = items[0] || {};
|
||||
|
||||
if (!name) {
|
||||
return this.error(this.t`Item with barcode ${barcode} not found.`);
|
||||
}
|
||||
|
||||
const quantity = isWeightEnabled
|
||||
? this.parseBarcode(
|
||||
barcode,
|
||||
unit as string,
|
||||
randomNumberLength + ItemBarcodeLength
|
||||
)
|
||||
: 1;
|
||||
|
||||
this.success(this.t`${name as string} quantity ${quantity} added.`);
|
||||
this.$emit('item-selected', name, quantity);
|
||||
},
|
||||
|
||||
parseBarcode(barcode: string, unitType: string, sliceDigit: number) {
|
||||
const weightRaw = parseInt(barcode.slice(sliceDigit), sliceDigit);
|
||||
|
||||
let itemQuantity = 0;
|
||||
|
||||
switch (unitType) {
|
||||
case 'Kg':
|
||||
itemQuantity = Math.floor(weightRaw / 1000);
|
||||
break;
|
||||
case 'Gram':
|
||||
itemQuantity = weightRaw;
|
||||
break;
|
||||
case 'Unit':
|
||||
case 'Meter':
|
||||
case 'Hour':
|
||||
case 'Day':
|
||||
itemQuantity = weightRaw;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown unit type!');
|
||||
}
|
||||
|
||||
return itemQuantity;
|
||||
},
|
||||
async scanListener({ key, code }: KeyboardEvent) {
|
||||
/**
|
||||
* Based under the assumption that
|
||||
* - Barcode scanners trigger keydown events
|
||||
* - Keydown events are triggered quicker than human can
|
||||
* i.e. at max 20ms between events
|
||||
* - Keydown events are triggered for barcode digits
|
||||
* - The sequence of digits might be punctuated by a return
|
||||
*/
|
||||
|
||||
const keyCode = Number(key);
|
||||
const isEnter = code === 'Enter';
|
||||
if (Number.isNaN(keyCode) && !isEnter) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEnter) {
|
||||
return await this.setItemFromBarcode();
|
||||
}
|
||||
|
||||
this.clearInterval();
|
||||
|
||||
this.barcode += key;
|
||||
this.timerId = setTimeout(async () => {
|
||||
await this.setItemFromBarcode();
|
||||
this.barcode = '';
|
||||
}, 20);
|
||||
},
|
||||
async setItemFromBarcode() {
|
||||
if (this.barcode.length < 12) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.selectItem(this.barcode);
|
||||
|
||||
this.barcode = '';
|
||||
this.clearInterval();
|
||||
},
|
||||
clearInterval() {
|
||||
if (this.timerId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(this.timerId);
|
||||
this.timerId = null;
|
||||
},
|
||||
error(message: string) {
|
||||
showToast({ type: 'error', message });
|
||||
},
|
||||
success(message: string) {
|
||||
showToast({ type: 'success', message });
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -98,7 +98,10 @@
|
||||
/>
|
||||
|
||||
<Barcode
|
||||
v-if="fyo.singles.InventorySettings?.enableBarcodes"
|
||||
v-if="
|
||||
fyo.singles.InventorySettings?.enableBarcodes &&
|
||||
!fyo.singles.POSSettings?.weightEnabledBarcode
|
||||
"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string) => {
|
||||
@ -106,6 +109,16 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<WeightEnabledBarcode
|
||||
v-if="fyo.singles.POSSettings?.weightEnabledBarcode"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string,qty:number) => {
|
||||
emitEvent('addItem', await getItem(name) as Item,qty as number);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ItemsTable
|
||||
@ -313,6 +326,7 @@ import ItemsTable from 'src/components/POS/Classic/ItemsTable.vue';
|
||||
import MultiLabelLink from 'src/components/Controls/MultiLabelLink.vue';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import SelectedItemTable from 'src/components/POS/Classic/SelectedItemTable.vue';
|
||||
import WeightEnabledBarcode from 'src/components/Controls/weightEnabledBarcode.vue';
|
||||
import FloatingLabelFloatInput from 'src/components/POS/FloatingLabelFloatInput.vue';
|
||||
import FloatingLabelCurrencyInput from 'src/components/POS/FloatingLabelCurrencyInput.vue';
|
||||
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
@ -336,6 +350,7 @@ export default defineComponent({
|
||||
SavedInvoiceModal,
|
||||
ClosePOSShiftModal,
|
||||
LoyaltyProgramModal,
|
||||
WeightEnabledBarcode,
|
||||
FloatingLabelFloatInput,
|
||||
FloatingLabelCurrencyInput,
|
||||
},
|
||||
@ -414,7 +429,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
emitEvent(
|
||||
eventName: PosEmits,
|
||||
...args: (string | boolean | Item | Money)[]
|
||||
...args: (string | boolean | Item | number | Money)[]
|
||||
) {
|
||||
this.$emit(eventName, ...args);
|
||||
},
|
||||
|
@ -255,7 +255,10 @@
|
||||
/>
|
||||
|
||||
<Barcode
|
||||
v-if="fyo.singles.InventorySettings?.enableBarcodes"
|
||||
v-if="
|
||||
fyo.singles.InventorySettings?.enableBarcodes &&
|
||||
!fyo.singles.POSSettings?.weightEnabledBarcode
|
||||
"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string) => {
|
||||
@ -263,6 +266,16 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<WeightEnabledBarcode
|
||||
v-if="fyo.singles.POSSettings?.weightEnabledBarcode"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string,qty:number) => {
|
||||
emitEvent('addItem', await getItem(name) as Item,qty as number);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ModernPOSItemsTable
|
||||
@ -321,6 +334,7 @@ import { POSItem, PosEmits, ItemQtyMap } from 'src/components/POS/types';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import ModernPOSItemsGrid from 'src/components/POS/Modern/ModernPOSItemsGrid.vue';
|
||||
import ModernPOSItemsTable from 'src/components/POS/Modern/ModernPOSItemsTable.vue';
|
||||
import WeightEnabledBarcode from 'src/components/Controls/weightEnabledBarcode.vue';
|
||||
import FloatingLabelFloatInput from 'src/components/POS/FloatingLabelFloatInput.vue';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import FloatingLabelCurrencyInput from 'src/components/POS/FloatingLabelCurrencyInput.vue';
|
||||
@ -346,6 +360,7 @@ export default defineComponent({
|
||||
ClosePOSShiftModal,
|
||||
LoyaltyProgramModal,
|
||||
ModernPOSItemsTable,
|
||||
WeightEnabledBarcode,
|
||||
FloatingLabelFloatInput,
|
||||
FloatingLabelCurrencyInput,
|
||||
ModernPOSSelectedItemTable,
|
||||
@ -430,7 +445,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
emitEvent(
|
||||
eventName: PosEmits,
|
||||
...args: (string | boolean | Item | Money)[]
|
||||
...args: (string | boolean | Item | number | Money)[]
|
||||
) {
|
||||
this.$emit(eventName, ...args);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user