Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: weight enabled barcode #1072

Merged
merged 12 commits into from
Jan 2, 2025
15 changes: 14 additions & 1 deletion models/inventory/Point of Sale/POSSettings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Doc } from 'fyo/model/doc';
import { FiltersMap } from 'fyo/model/types';
import { FiltersMap, HiddenMap } from 'fyo/model/types';
import {
AccountRootTypeEnum,
AccountTypeEnum,
Expand All @@ -10,6 +10,11 @@ export class POSSettings extends Doc {
inventory?: string;
cashAccount?: string;
writeOffAccount?: string;
weightEnabledBarcode?: boolean;
checkDigits?: number;
itemCodeDigits?: number;
itemWeightDigits?: number;

posUI?: 'Classic' | 'Modern';

static filters: FiltersMap = {
Expand All @@ -19,4 +24,12 @@ export class POSSettings extends Doc {
isGroup: false,
}),
};

hidden: HiddenMap = {
weightEnabledBarcode: () =>
!this.fyo.singles.InventorySettings?.enableBarcodes,
checkDigits: () => !this.fyo.singles.InventorySettings?.enableBarcodes,
itemCodeDigits: () => !this.fyo.singles.InventorySettings?.enableBarcodes,
itemWeightDigits: () => !this.fyo.singles.InventorySettings?.enableBarcodes,
};
}
28 changes: 28 additions & 0 deletions schemas/app/inventory/Point of Sale/POSSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,34 @@
"default": "Classic",
"required": true,
"section": "Default"
},
{
"fieldname": "weightEnabledBarcode",
"label": "Weigth Enabled Barcode",
"fieldtype": "Check",
"default": false,
"section": "Barcode"
},
{
"fieldname": "checkDigits",
"label": "Check Digits",
"fieldtype": "Int",
"default": 0,
"section": "Barcode"
},
{
"fieldname": "itemCodeDigits",
"label": "Item Code Digits",
"fieldtype": "Int",
"default": 0,
"section": "Barcode"
},
{
"fieldname": "itemWeightDigits",
"label": "item Weight Digits",
"fieldtype": "Int",
"default": 0,
"section": "Barcode"
}
]
}
6 changes: 5 additions & 1 deletion src/components/Controls/Barcode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
border
rounded
bg-gray-50
dark:border-gray-800 dark:bg-gray-890 dark:focus-within:bg-gray-900
dark:text-gray-200
dark:border-gray-800
dark:bg-gray-890
dark:focus-within:bg-gray-900
focus-within:bg-gray-100
"
>
Expand Down Expand Up @@ -84,6 +87,7 @@ export default defineComponent({
})) as { name: string }[];

const name = items?.[0]?.name;

if (!name) {
return this.error(this.t`Item with barcode ${barcode} not found.`);
}
Expand Down
203 changes: 203 additions & 0 deletions src/components/Controls/WeightEnabledBarcode.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<template>
<div
class="
px-2
w-36
flex
items-center
border
rounded
bg-gray-50
dark:text-gray-200
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({
name: 'WeightEnabledBarcode',
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 checkDigits = this.fyo.singles.POSSettings?.checkDigits as number;
const itemCodeDigits = this.fyo.singles.POSSettings
?.itemCodeDigits as number;
const itemWeightDigits = this.fyo.singles.POSSettings
?.itemWeightDigits as number;

if (code.length !== checkDigits + itemCodeDigits + itemWeightDigits) {
return this.error(this.t`Barcode ${barcode} has an invalid length.`);
}

const filters: Record<string, string> = isWeightEnabled
? {
barcode: barcode.slice(checkDigits, checkDigits + itemCodeDigits),
}
: { barcode };
const fields = isWeightEnabled
? ['name', 'unit', 'trackItem']
: ['name', 'trackItem'];

const items =
(await this.fyo.db.getAll('Item', { filters, fields })) || [];
const { name, unit, trackItem } = items[0] || {};

if (!trackItem) {
return this.error(
this.t`Item ${name as string} is not an Inventory Item.`
);
}

if (!name) {
return this.error(this.t`Item with barcode ${barcode} not found.`);
}

const quantity = isWeightEnabled
? this.parseBarcode(
barcode,
unit as string,
checkDigits + itemCodeDigits
)
: 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));

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>
19 changes: 17 additions & 2 deletions src/pages/POS/ClassicPOS.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,27 @@
/>

<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) => {
emitEvent('addItem', await getItem(name) as Item);
}
"
/>

<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
Expand Down Expand Up @@ -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';
Expand All @@ -336,6 +350,7 @@ export default defineComponent({
SavedInvoiceModal,
ClosePOSShiftModal,
LoyaltyProgramModal,
WeightEnabledBarcode,
FloatingLabelFloatInput,
FloatingLabelCurrencyInput,
},
Expand Down Expand Up @@ -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);
},
Expand Down
19 changes: 17 additions & 2 deletions src/pages/POS/ModernPOS.vue
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,27 @@
/>

<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) => {
emitEvent('addItem', await getItem(name) as Item);
}
"
/>

<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
Expand Down Expand Up @@ -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';
Expand All @@ -346,6 +360,7 @@ export default defineComponent({
ClosePOSShiftModal,
LoyaltyProgramModal,
ModernPOSItemsTable,
WeightEnabledBarcode,
FloatingLabelFloatInput,
FloatingLabelCurrencyInput,
ModernPOSSelectedItemTable,
Expand Down Expand Up @@ -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);
},
Expand Down
Loading