mirror of
https://github.com/frappe/books.git
synced 2025-02-02 12:08:27 +00:00
Merge pull request #1000 from AbleKSaju/feat-newPos-UI
feat: POS modern UI
This commit is contained in:
commit
8d74236d37
@ -1,5 +1,7 @@
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import {
|
||||
AccountRootType,
|
||||
AccountRootTypeEnum,
|
||||
} from './baseModels/Account/types';
|
||||
import {
|
||||
Action,
|
||||
ColumnConfig,
|
||||
@ -7,31 +9,30 @@ import {
|
||||
LeadStatus,
|
||||
RenderData,
|
||||
} from 'fyo/model/types';
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { InvoiceStatus, ModelNameEnum } from './types';
|
||||
|
||||
import { ApplicablePricingRules } from './baseModels/Invoice/types';
|
||||
import { AppliedCouponCodes } from './baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
import { CollectionRulesItems } from './baseModels/CollectionRulesItems/CollectionRulesItems';
|
||||
import { CouponCode } from './baseModels/CouponCode/CouponCode';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Money } from 'pesa';
|
||||
import { safeParseFloat } from 'utils/index';
|
||||
import { Router } from 'vue-router';
|
||||
import {
|
||||
AccountRootType,
|
||||
AccountRootTypeEnum,
|
||||
} from './baseModels/Account/types';
|
||||
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Invoice } from './baseModels/Invoice/Invoice';
|
||||
import { Lead } from './baseModels/Lead/Lead';
|
||||
import { LoyaltyProgram } from './baseModels/LoyaltyProgram/LoyaltyProgram';
|
||||
import { Money } from 'pesa';
|
||||
import { Party } from './baseModels/Party/Party';
|
||||
import { PricingRule } from './baseModels/PricingRule/PricingRule';
|
||||
import { Router } from 'vue-router';
|
||||
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
|
||||
import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
|
||||
import { StockMovement } from './inventory/StockMovement';
|
||||
import { StockTransfer } from './inventory/StockTransfer';
|
||||
import { InvoiceStatus, ModelNameEnum } from './types';
|
||||
import { Lead } from './baseModels/Lead/Lead';
|
||||
import { PricingRule } from './baseModels/PricingRule/PricingRule';
|
||||
import { ApplicablePricingRules } from './baseModels/Invoice/types';
|
||||
import { LoyaltyProgram } from './baseModels/LoyaltyProgram/LoyaltyProgram';
|
||||
import { CollectionRulesItems } from './baseModels/CollectionRulesItems/CollectionRulesItems';
|
||||
import { isPesa } from 'fyo/utils';
|
||||
import { Party } from './baseModels/Party/Party';
|
||||
import { CouponCode } from './baseModels/CouponCode/CouponCode';
|
||||
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
|
||||
import { AppliedCouponCodes } from './baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { isPesa } from 'fyo/utils';
|
||||
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
||||
import { safeParseFloat } from 'utils/index';
|
||||
|
||||
export function getQuoteActions(
|
||||
fyo: Fyo,
|
||||
@ -763,6 +764,19 @@ export function getLoyaltyProgramTier(
|
||||
return loyaltyProgramTier;
|
||||
}
|
||||
|
||||
export async function updatePricingRuleItem(doc: SalesInvoice) {
|
||||
const pricingRule = (await getPricingRule(doc)) as ApplicablePricingRules[];
|
||||
|
||||
let appliedPricingRuleCount = doc?.pricingRuleDetail?.length;
|
||||
|
||||
if (appliedPricingRuleCount !== pricingRule?.length) {
|
||||
appliedPricingRuleCount = pricingRule?.length;
|
||||
|
||||
await doc?.appendPricingRuleDetail(pricingRule);
|
||||
await doc?.applyProductDiscount();
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeLoyaltyPoint(doc: Doc) {
|
||||
if (!doc.loyaltyProgram) {
|
||||
return;
|
||||
|
@ -9,6 +9,7 @@ export class POSSettings extends Doc {
|
||||
inventory?: string;
|
||||
cashAccount?: string;
|
||||
writeOffAccount?: string;
|
||||
posUI?: 'Classic' | 'Modern';
|
||||
|
||||
static filters: FiltersMap = {
|
||||
cashAccount: () => ({
|
||||
|
@ -31,6 +31,24 @@
|
||||
"create": true,
|
||||
"default": "Write Off",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "posUI",
|
||||
"label": "Pos Ui",
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
{
|
||||
"value": "Classic",
|
||||
"label": "Classic"
|
||||
},
|
||||
{
|
||||
"value": "Modern",
|
||||
"label": "Modern"
|
||||
}
|
||||
],
|
||||
"default": "Classic",
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
px-2
|
||||
w-36
|
||||
flex
|
||||
items-center
|
||||
border
|
||||
w-36
|
||||
rounded
|
||||
px-2
|
||||
bg-gray-50
|
||||
dark:bg-gray-890
|
||||
dark:border-gray-800 dark:bg-gray-890 dark:focus-within:bg-gray-900
|
||||
focus-within:bg-gray-100
|
||||
dark:focus-within:bg-gray-900
|
||||
"
|
||||
>
|
||||
<input
|
||||
|
@ -15,7 +15,7 @@
|
||||
:tabindex="isReadOnly ? '-1' : '0'"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
@input="(e) => $emit('input', e)"
|
||||
@input="(e:Event) => $emit('input', e)"
|
||||
/>
|
||||
<div
|
||||
v-show="!showInput"
|
||||
@ -47,6 +47,17 @@ export default defineComponent({
|
||||
currencySymbol: '',
|
||||
};
|
||||
},
|
||||
props: {
|
||||
focusInput: Boolean,
|
||||
},
|
||||
created() {
|
||||
if (this.focusInput) {
|
||||
this.showInput = true;
|
||||
nextTick(() => {
|
||||
this.focus();
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedValue() {
|
||||
const value = this.parse(this.value);
|
||||
|
@ -1,51 +1,61 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
flex flex-col
|
||||
gap-4
|
||||
p-4
|
||||
py-2
|
||||
w-full
|
||||
flex flex-col
|
||||
items-center
|
||||
mt-4
|
||||
px-2
|
||||
rounded-t-md
|
||||
text-black
|
||||
w-full
|
||||
overflow-y-auto
|
||||
custom-scroll custom-scroll-thumb2
|
||||
"
|
||||
style="height: 80vh"
|
||||
style="height: 83vh"
|
||||
>
|
||||
<!-- Items Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 w-full">
|
||||
<div
|
||||
class="
|
||||
gap-2
|
||||
w-full
|
||||
grid grid-cols-1
|
||||
md:grid-cols-2
|
||||
lg:grid-cols-3
|
||||
xl:grid-cols-4
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
border border-gray-300
|
||||
dark:border-gray-800
|
||||
p-1
|
||||
border border-gray-300
|
||||
flex flex-col
|
||||
text-sm text-center
|
||||
dark:border-gray-800
|
||||
"
|
||||
@click="handleChange(item as POSItem)"
|
||||
v-for="item in items as POSItem[]"
|
||||
:key="item.name"
|
||||
>
|
||||
<div class="self-center w-32 h-32 rounded-lg mb-1">
|
||||
<div class="relative">
|
||||
<div class="self-center w-32 h-32 p-1 rounded-lg">
|
||||
<div class="relative w-full h-full p-2">
|
||||
<img
|
||||
v-if="item.image"
|
||||
:src="item.image"
|
||||
alt=""
|
||||
class="rounded-lg w-32 h-32 object-cover"
|
||||
class="rounded-lg w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
rounded-lg
|
||||
w-32
|
||||
h-32
|
||||
flex
|
||||
w-full
|
||||
h-full
|
||||
bg-gray-100
|
||||
dark:bg-gray-850
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
dark:bg-gray-850
|
||||
"
|
||||
>
|
||||
<p class="text-4xl font-semibold text-gray-400 select-none">
|
||||
@ -53,7 +63,17 @@
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="absolute top-1 right-1 rounded-full p-1"
|
||||
class="
|
||||
absolute
|
||||
top-1
|
||||
right-1
|
||||
rounded-full
|
||||
w-6
|
||||
h-6
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
"
|
||||
:class="
|
||||
item.availableQty > 0
|
||||
? 'bg-green-100 text-green-900'
|
||||
@ -79,8 +99,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { POSItem } from './types';
|
||||
import { POSItem } from '../types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ItemsGrid',
|
@ -29,7 +29,10 @@
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<div class="overflow-y-auto" style="height: 80vh">
|
||||
<div
|
||||
class="overflow-y-auto custom-scroll custom-scroll-thumb2"
|
||||
style="height: 70vh"
|
||||
>
|
||||
<Row
|
||||
v-if="items"
|
||||
v-for="row in items as any"
|
||||
@ -64,12 +67,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import FormControl from '../Controls/FormControl.vue';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Row from 'src/components/Row.vue';
|
||||
import { isNumeric } from 'src/utils';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Field } from 'schemas/types';
|
||||
import { POSItem } from './types';
|
||||
import { POSItem } from '../types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ItemsTable',
|
@ -262,18 +262,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Currency from '../Controls/Currency.vue';
|
||||
import Data from '../Controls/Data.vue';
|
||||
import Float from '../Controls/Float.vue';
|
||||
import Int from '../Controls/Int.vue';
|
||||
import Link from '../Controls/Link.vue';
|
||||
import Text from '../Controls/Text.vue';
|
||||
import Currency from 'src/components/Controls/Currency.vue';
|
||||
import Data from 'src/components/Controls/Data.vue';
|
||||
import Float from 'src/components/Controls/Float.vue';
|
||||
import Int from 'src/components/Controls/Int.vue';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import Text from 'src/components/Controls/Text.vue';
|
||||
import { inject } from 'vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { defineComponent } from 'vue';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { Money } from 'pesa';
|
||||
import { DiscountType } from './types';
|
||||
import { DiscountType } from '../types';
|
||||
import { validateSerialNumberCount } from 'src/utils/pos';
|
||||
import { getPricingRule } from 'models/helpers';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
@ -11,7 +11,7 @@
|
||||
w-full
|
||||
flex
|
||||
items-center
|
||||
mt-4
|
||||
mt-2
|
||||
"
|
||||
>
|
||||
<div
|
||||
@ -60,10 +60,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import FormContainer from '../FormContainer.vue';
|
||||
import FormControl from '../Controls/FormControl.vue';
|
||||
import Link from '../Controls/Link.vue';
|
||||
import Row from '../Row.vue';
|
||||
import FormContainer from 'src/components/FormContainer.vue';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import Row from 'src/components/Row.vue';
|
||||
import RowEditForm from 'src/pages/CommonForm/RowEditForm.vue';
|
||||
import SelectedItemRow from './SelectedItemRow.vue';
|
||||
import { isNumeric } from 'src/utils';
|
131
src/components/POS/Modern/ModernPOSItemsGrid.vue
Normal file
131
src/components/POS/Modern/ModernPOSItemsGrid.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
flex flex-col
|
||||
items-center
|
||||
gap-4
|
||||
my-3
|
||||
px-4
|
||||
py-2
|
||||
rounded-t-md
|
||||
text-black
|
||||
w-full
|
||||
overflow-y-auto
|
||||
custom-scroll custom-scroll-thumb2
|
||||
"
|
||||
style="height: 80vh"
|
||||
>
|
||||
<!-- Items Grid -->
|
||||
<div
|
||||
class="
|
||||
gap-2
|
||||
w-full
|
||||
grid grid-cols-1
|
||||
sm:grid-cols-2
|
||||
md:grid-cols-4
|
||||
lg:grid-cols-6
|
||||
xl:grid-cols-7'
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
p-1
|
||||
border border-gray-300
|
||||
dark:border-gray-800
|
||||
flex flex-col
|
||||
text-sm text-center
|
||||
"
|
||||
@click="handleChange(item as POSItem)"
|
||||
v-for="item in items as POSItem[]"
|
||||
:key="item.name"
|
||||
>
|
||||
<div class="self-center w-32 h-32 p-1 rounded-lg">
|
||||
<div class="relative w-full h-full p-2">
|
||||
<img
|
||||
v-if="item.image"
|
||||
:src="item.image"
|
||||
alt=""
|
||||
class="rounded-lg w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
rounded-lg
|
||||
bg-gray-100
|
||||
w-full
|
||||
h-full
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
dark:bg-gray-850
|
||||
"
|
||||
>
|
||||
<p class="text-4xl font-semibold text-gray-400 select-none">
|
||||
{{ getExtractedWords(item.name) }}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="
|
||||
w-6
|
||||
h-6
|
||||
top-1
|
||||
right-1
|
||||
absolute
|
||||
rounded-full
|
||||
flex
|
||||
justify-center
|
||||
items-center
|
||||
"
|
||||
:class="
|
||||
item.availableQty > 0
|
||||
? 'bg-green-100 text-green-900'
|
||||
: 'bg-red-100 text-red-900'
|
||||
"
|
||||
>
|
||||
{{ item.availableQty }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium dark:text-white">{{ item.name }}</h3>
|
||||
|
||||
<p class="text-lg font-medium dark:text-white">
|
||||
{{
|
||||
item.rate ? fyo.currencySymbols[item.rate.getCurrency()] : undefined
|
||||
}}
|
||||
{{ item.rate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { POSItem } from '../types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModernPOSItemsGrid',
|
||||
emits: ['addItem', 'updateValues'],
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
},
|
||||
itemQtyMap: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExtractedWords(item: string) {
|
||||
const initials = item.split(' ').map((word) => {
|
||||
return word[0].toUpperCase();
|
||||
});
|
||||
return initials.join('');
|
||||
},
|
||||
handleChange(value: POSItem) {
|
||||
this.$emit('addItem', value);
|
||||
this.$emit('updateValues');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
192
src/components/POS/Modern/ModernPOSItemsTable.vue
Normal file
192
src/components/POS/Modern/ModernPOSItemsTable.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<div
|
||||
class="w-1/2 overflow-y-auto custom-scroll custom-scroll-thumb2"
|
||||
style="height: 81vh"
|
||||
>
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="
|
||||
mt-2
|
||||
px-2
|
||||
w-full
|
||||
flex
|
||||
items-center
|
||||
border
|
||||
rounded-t-md
|
||||
text-gray-600
|
||||
dark:border-gray-800 dark:text-gray-400
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
class="flex items-center p-2 text-lg"
|
||||
:class="{
|
||||
'ms-auto': isNumeric(df as Field),
|
||||
}"
|
||||
>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<Row
|
||||
v-for="row in firstColumnItems as POSItem[]"
|
||||
:key="row.id"
|
||||
:ratio="ratio"
|
||||
:border="true"
|
||||
class="
|
||||
px-2
|
||||
w-full
|
||||
border-b border-x
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
group
|
||||
h-row-mid
|
||||
hover:bg-gray-25
|
||||
dark:border-gray-800 dark:bg-gray-890
|
||||
"
|
||||
@click="handleChange(row)"
|
||||
>
|
||||
<FormControl
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
size="large"
|
||||
:df="df"
|
||||
:value="row[df.fieldname]"
|
||||
:readOnly="true"
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-1/2 overflow-y-auto custom-scroll custom-scroll-thumb2"
|
||||
style="height: calc(80vh - 20rem)"
|
||||
>
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="
|
||||
mt-2
|
||||
px-2
|
||||
w-full
|
||||
flex
|
||||
items-center
|
||||
border
|
||||
rounded-t-md
|
||||
text-gray-600
|
||||
dark:border-gray-800 dark:text-gray-400
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
class="flex items-center p-2 text-lg"
|
||||
:class="{
|
||||
'ms-auto': isNumeric(df as Field),
|
||||
}"
|
||||
>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</Row>
|
||||
<Row
|
||||
v-for="row in secondColumnItems as POSItem[]"
|
||||
:key="row.id"
|
||||
:ratio="ratio"
|
||||
:border="true"
|
||||
class="
|
||||
px-2
|
||||
w-full
|
||||
border-b border-x
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
group
|
||||
h-row-mid
|
||||
hover:bg-gray-25
|
||||
dark:bg-gray-890 dark:border-gray-800
|
||||
"
|
||||
@click="handleChange(row)"
|
||||
>
|
||||
<FormControl
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
size="large"
|
||||
:df="df"
|
||||
:value="row[df.fieldname]"
|
||||
:readOnly="true"
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Row from 'src/components/Row.vue';
|
||||
import { isNumeric } from 'src/utils';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Field } from 'schemas/types';
|
||||
import { POSItem } from '../types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModernPOSItemsTable',
|
||||
components: { FormControl, Row },
|
||||
emits: ['addItem', 'updateValues'],
|
||||
props: {
|
||||
items: Array,
|
||||
itemQtyMap: Object,
|
||||
},
|
||||
computed: {
|
||||
ratio() {
|
||||
return [1, 1, 0.6, 0.7];
|
||||
},
|
||||
tableFields() {
|
||||
return [
|
||||
{
|
||||
fieldname: 'name',
|
||||
fieldtype: 'Data',
|
||||
label: 'Item',
|
||||
placeholder: 'Item',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
fieldname: 'rate',
|
||||
label: 'Rate',
|
||||
placeholder: 'Rate',
|
||||
fieldtype: 'Currency',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
fieldname: 'availableQty',
|
||||
label: 'Qty',
|
||||
placeholder: 'Available Qty',
|
||||
fieldtype: 'Float',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
fieldname: 'unit',
|
||||
label: 'Unit',
|
||||
placeholder: 'Unit',
|
||||
fieldtype: 'Data',
|
||||
target: 'UOM',
|
||||
readOnly: true,
|
||||
},
|
||||
] as Field[];
|
||||
},
|
||||
firstColumnItems() {
|
||||
return this.items?.slice(0, Math.ceil(this.items.length / 2));
|
||||
},
|
||||
secondColumnItems() {
|
||||
return this.items?.slice(Math.ceil(this.items.length / 2));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange(value: POSItem) {
|
||||
this.$emit('addItem', value);
|
||||
this.$emit('updateValues');
|
||||
},
|
||||
isNumeric,
|
||||
},
|
||||
});
|
||||
</script>
|
339
src/components/POS/Modern/ModernPOSSelectedItemRow.vue
Normal file
339
src/components/POS/Modern/ModernPOSSelectedItemRow.vue
Normal file
@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<div>
|
||||
<feather-icon
|
||||
:name="isExapanded ? 'chevron-up' : 'chevron-down'"
|
||||
class="w-4 h-4 inline-flex"
|
||||
@click="isExapanded = !isExapanded"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="relative" @click="isExapanded = !isExapanded">
|
||||
<Link
|
||||
:df="{
|
||||
fieldname: 'item',
|
||||
fieldtype: 'Data',
|
||||
label: 'item',
|
||||
}"
|
||||
:class="row.isFreeItem ? 'mt-2' : ''"
|
||||
size="small"
|
||||
:border="false"
|
||||
:value="row.item"
|
||||
:read-only="true"
|
||||
/>
|
||||
<p
|
||||
v-if="row.isFreeItem"
|
||||
class="absolute flex top-0 font-medium text-xs ml-2 text-green-800"
|
||||
style="font-size: 0.6rem"
|
||||
>
|
||||
{{ row.pricingRule }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Int
|
||||
:df="{
|
||||
fieldname: 'quantity',
|
||||
fieldtype: 'Int',
|
||||
label: 'Quantity',
|
||||
}"
|
||||
size="small"
|
||||
:border="false"
|
||||
:value="row.quantity"
|
||||
:read-only="true"
|
||||
/>
|
||||
|
||||
<Currency
|
||||
:df="{
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'rate',
|
||||
label: 'rate',
|
||||
}"
|
||||
size="small"
|
||||
:border="false"
|
||||
:value="row.rate"
|
||||
:read-only="true"
|
||||
/>
|
||||
|
||||
<Currency
|
||||
:df="{
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'amount',
|
||||
label: 'Amount',
|
||||
}"
|
||||
size="small"
|
||||
:border="false"
|
||||
:value="row.amount"
|
||||
:read-only="true"
|
||||
/>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<feather-icon
|
||||
name="trash"
|
||||
class="w-4 text-xl text-red-500"
|
||||
@click="removeAddedItem(row)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div></div>
|
||||
|
||||
<template v-if="isExapanded">
|
||||
<div class="rounded-md grid grid-cols-4 my-3" style="width: 27vw">
|
||||
<div class="px-4 col-span-2">
|
||||
<Float
|
||||
:df="{
|
||||
fieldname: 'quantity',
|
||||
fieldtype: 'Float',
|
||||
label: 'Quantity',
|
||||
}"
|
||||
@click="handleOpenKeyboard(row, 'quantity')"
|
||||
size="medium"
|
||||
:min="0"
|
||||
:border="true"
|
||||
:show-label="true"
|
||||
:value="row.quantity"
|
||||
:read-only="isReadOnly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="px-4 col-span-2">
|
||||
<Link
|
||||
v-if="isUOMConversionEnabled"
|
||||
:df="{
|
||||
fieldname: 'transferUnit',
|
||||
fieldtype: 'Link',
|
||||
target: 'UOM',
|
||||
label: t`Transfer Unit`,
|
||||
}"
|
||||
size="medium"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:value="row.transferUnit"
|
||||
:read-only="isReadOnly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pt-6 col-span-2">
|
||||
<Int
|
||||
v-if="isUOMConversionEnabled"
|
||||
:df="{
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'transferQuantity',
|
||||
label: 'Transfer Quantity',
|
||||
}"
|
||||
@click="!isReadOnly && handleOpenKeyboard(row, 'transferQuantity')"
|
||||
size="medium"
|
||||
:border="true"
|
||||
:show-label="true"
|
||||
:value="row.transferQuantity"
|
||||
:read-only="isReadOnly"
|
||||
/>
|
||||
</div>
|
||||
<div class="px-4 pt-6 col-span-2">
|
||||
<Currency
|
||||
:df="{
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'rate',
|
||||
label: 'Rate',
|
||||
}"
|
||||
@click="!isReadOnly && handleOpenKeyboard(row, 'rate')"
|
||||
size="medium"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:value="row.rate"
|
||||
:read-only="isReadOnly"
|
||||
/>
|
||||
</div>
|
||||
<div class="px-4 col-span-2 mt-5">
|
||||
<Currency
|
||||
v-if="isDiscountingEnabled"
|
||||
:df="{
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'discountAmount',
|
||||
label: 'Discount Amount',
|
||||
}"
|
||||
@click="handleOpenKeyboard(row, 'itemDiscountAmount')"
|
||||
class="col-span-2"
|
||||
size="medium"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:value="row.itemDiscountAmount"
|
||||
:read-only="row.itemDiscountPercent as number > 0 || isReadOnly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="px-4 col-span-2 mt-5">
|
||||
<Float
|
||||
v-if="isDiscountingEnabled"
|
||||
:df="{
|
||||
fieldtype: 'Float',
|
||||
fieldname: 'itemDiscountPercent',
|
||||
label: 'Discount Percent',
|
||||
}"
|
||||
@click="handleOpenKeyboard(row, 'itemDiscountPercent')"
|
||||
size="medium"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:value="row.itemDiscountPercent"
|
||||
:read-only="!row.itemDiscountAmount?.isZero() || isReadOnly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="row.links?.item && row.links?.item.hasBatch"
|
||||
class="px-4 pt-6 col-span-2"
|
||||
>
|
||||
<Link
|
||||
:df="{
|
||||
fieldname: 'batch',
|
||||
fieldtype: 'Link',
|
||||
target: 'Batch',
|
||||
label: t`Batch`,
|
||||
}"
|
||||
size="medium"
|
||||
:value="row.batch"
|
||||
:border="true"
|
||||
:show-label="true"
|
||||
:read-only="false"
|
||||
@change="(value:string) => setBatch(value)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="row.links?.item && row.links?.item.hasBatch"
|
||||
class="px-4 pt-6 col-span-2"
|
||||
>
|
||||
<Float
|
||||
:df="{
|
||||
fieldname: 'availableQtyInBatch',
|
||||
fieldtype: 'Float',
|
||||
label: t`Qty in Batch`,
|
||||
}"
|
||||
size="medium"
|
||||
:min="0"
|
||||
:value="availableQtyInBatch"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="hasSerialNumber" class="px-4 pt-6 col-span-4">
|
||||
<Text
|
||||
:df="{
|
||||
label: t`Serial Number`,
|
||||
fieldtype: 'Text',
|
||||
fieldname: 'serialNumber',
|
||||
}"
|
||||
:value="row.serialNumber"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:required="hasSerialNumber"
|
||||
@change="(value:string)=> setSerialNumber(value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Currency from 'src/components/Controls/Currency.vue';
|
||||
import Data from 'src/components/Controls/Data.vue';
|
||||
import Float from 'src/components/Controls/Float.vue';
|
||||
import Int from 'src/components/Controls/Int.vue';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import Text from 'src/components/Controls/Text.vue';
|
||||
import { inject } from 'vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { defineComponent } from 'vue';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { Money } from 'pesa';
|
||||
import { validateSerialNumberCount } from 'src/utils/pos';
|
||||
import { updatePricingRuleItem } from 'models/helpers';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModernPOSSelectedItemRow',
|
||||
components: { Currency, Data, Float, Int, Link, Text },
|
||||
props: {
|
||||
row: { type: SalesInvoiceItem, required: true },
|
||||
},
|
||||
emits: ['toggleModal', 'runSinvFormulas', 'selectedRow'],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
isDiscountingEnabled: inject('isDiscountingEnabled') as boolean,
|
||||
itemSerialNumbers: inject('itemSerialNumbers') as {
|
||||
[item: string]: string;
|
||||
},
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExapanded: false,
|
||||
batches: [] as string[],
|
||||
availableQtyInBatch: 0,
|
||||
|
||||
defaultRate: this.row.rate as Money,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isUOMConversionEnabled(): boolean {
|
||||
return !!fyo.singles.InventorySettings?.enableUomConversions;
|
||||
},
|
||||
hasSerialNumber(): boolean {
|
||||
return !!(this.row.links?.item && this.row.links?.item.hasSerialNumber);
|
||||
},
|
||||
isReadOnly() {
|
||||
return this.row.isFreeItem;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleOpenKeyboard(row: SalesInvoiceItem, field: string) {
|
||||
if (this.isReadOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('selectedRow', row, field);
|
||||
this.$emit('toggleModal', 'Keyboard');
|
||||
},
|
||||
async getAvailableQtyInBatch(): Promise<number> {
|
||||
if (!this.row.batch) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
(await fyo.db.getStockQuantity(
|
||||
this.row.item as string,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
this.row.batch
|
||||
)) ?? 0
|
||||
);
|
||||
},
|
||||
async setBatch(batch: string) {
|
||||
this.row.set('batch', batch);
|
||||
this.availableQtyInBatch = await this.getAvailableQtyInBatch();
|
||||
},
|
||||
setSerialNumber(serialNumber: string) {
|
||||
if (!serialNumber) {
|
||||
return;
|
||||
}
|
||||
this.itemSerialNumbers[this.row.item as string] = serialNumber;
|
||||
|
||||
validateSerialNumberCount(
|
||||
serialNumber,
|
||||
this.row.quantity ?? 0,
|
||||
this.row.item!
|
||||
);
|
||||
},
|
||||
async removeAddedItem(row: SalesInvoiceItem) {
|
||||
this.row.parentdoc?.remove('items', row?.idx as number);
|
||||
|
||||
if (!row.isFreeItem) {
|
||||
await updatePricingRuleItem(this.row.parentdoc as SalesInvoice);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
150
src/components/POS/Modern/ModernPOSSelectedItemTable.vue
Normal file
150
src/components/POS/Modern/ModernPOSSelectedItemTable.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="
|
||||
w-full
|
||||
px-2
|
||||
mt-2
|
||||
border
|
||||
rounded-t
|
||||
text-gray-600
|
||||
dark:border-gray-800 dark:text-gray-400
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-if="tableFields"
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
class="text-lg flex m-2"
|
||||
:class="{
|
||||
'ms-auto': isNumeric(df as Field),
|
||||
}"
|
||||
>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<div
|
||||
class="overflow-auto custom-scroll custom-scroll-thumb1"
|
||||
style="height: calc(90vh - 25rem)"
|
||||
>
|
||||
<Row
|
||||
v-for="row in sinvDoc.items"
|
||||
:ratio="ratio"
|
||||
class="
|
||||
p-2
|
||||
border
|
||||
w-full
|
||||
hover:bg-gray-25
|
||||
dark:border-gray-800 dark:bg-gray-890
|
||||
"
|
||||
>
|
||||
<ModernPOSSelectedItemRow
|
||||
:row="(row as SalesInvoiceItem)"
|
||||
@selected-row="selectedItemRow"
|
||||
@run-sinv-formulas="runSinvFormulas"
|
||||
@toggle-modal="handleToggleModal"
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import FormContainer from 'src/components/FormContainer.vue';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import Row from 'src/components/Row.vue';
|
||||
import RowEditForm from 'src/pages/CommonForm/RowEditForm.vue';
|
||||
import ModernPOSSelectedItemRow from './ModernPOSSelectedItemRow.vue';
|
||||
import { isNumeric } from 'src/utils';
|
||||
import { inject, defineComponent } from 'vue';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import { Field } from 'schemas/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModernPOSSelectedItemTable',
|
||||
components: {
|
||||
FormContainer,
|
||||
FormControl,
|
||||
Link,
|
||||
Row,
|
||||
RowEditForm,
|
||||
ModernPOSSelectedItemRow,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
sinvDoc: inject('sinvDoc') as SalesInvoice,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExapanded: false,
|
||||
};
|
||||
},
|
||||
emits: ['toggleModal', 'selectedRow'],
|
||||
computed: {
|
||||
ratio() {
|
||||
return [0.1, 0.8, 0.4, 0.8, 0.8, 0.3];
|
||||
},
|
||||
tableFields() {
|
||||
return [
|
||||
{
|
||||
fieldname: 'toggler',
|
||||
fieldtype: 'Link',
|
||||
label: ' ',
|
||||
},
|
||||
{
|
||||
fieldname: 'item',
|
||||
fieldtype: 'Link',
|
||||
label: 'Item',
|
||||
placeholder: 'Item',
|
||||
required: true,
|
||||
schemaName: 'Item',
|
||||
},
|
||||
{
|
||||
fieldname: 'quantity',
|
||||
label: 'Quantity',
|
||||
placeholder: 'Quantity',
|
||||
fieldtype: 'Int',
|
||||
required: true,
|
||||
schemaName: '',
|
||||
},
|
||||
{
|
||||
fieldname: 'rate',
|
||||
label: 'Rate',
|
||||
placeholder: 'Rate',
|
||||
fieldtype: 'Currency',
|
||||
required: true,
|
||||
schemaName: '',
|
||||
},
|
||||
{
|
||||
fieldname: 'amount',
|
||||
label: 'Amount',
|
||||
placeholder: 'Amount',
|
||||
fieldtype: 'Currency',
|
||||
required: true,
|
||||
schemaName: '',
|
||||
},
|
||||
{
|
||||
fieldname: 'removeItem',
|
||||
fieldtype: 'Link',
|
||||
label: ' ',
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleToggleModal(modal: string) {
|
||||
this.$emit('toggleModal', modal);
|
||||
},
|
||||
async runSinvFormulas() {
|
||||
await this.sinvDoc.runFormulas();
|
||||
},
|
||||
selectedItemRow(row: SalesInvoiceItem, field: string) {
|
||||
this.$emit('selectedRow', row, field);
|
||||
},
|
||||
isNumeric,
|
||||
},
|
||||
});
|
||||
</script>
|
@ -9,15 +9,16 @@ export type ItemSerialNumbers = { [item: string]: string };
|
||||
export type DiscountType = 'percent' | 'amount';
|
||||
|
||||
export type ModalName =
|
||||
| 'ShiftOpen'
|
||||
| 'ShiftClose'
|
||||
| 'Keyboard'
|
||||
| 'Payment'
|
||||
| 'ShiftClose'
|
||||
| 'LoyaltyProgram'
|
||||
| 'SavedInvoice'
|
||||
| 'RouteToInvoiceList'
|
||||
| 'Alert'
|
||||
| 'CouponCode';
|
||||
|
||||
export interface POSItem {
|
||||
id?: number;
|
||||
image?: string;
|
||||
name: string;
|
||||
rate: Money;
|
||||
|
@ -10,7 +10,7 @@
|
||||
<div class="flex col-span-2 gap-5">
|
||||
<Button
|
||||
class="py-5 w-full bg-red-500 dark:bg-red-700"
|
||||
@click="$emit('toggleModal', 'RouteToInvoiceList')"
|
||||
@click="$emit('toggleModal', 'Alert')"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
@ -23,7 +23,7 @@
|
||||
class="w-full py-5 bg-green-500 dark:bg-green-700"
|
||||
@click="
|
||||
routeTo('/list/SalesInvoice');
|
||||
$emit('toggleModal', 'RouteToInvoiceList');
|
||||
$emit('toggleModal', 'Alert');
|
||||
"
|
||||
>
|
||||
<slot>
|
||||
@ -49,7 +49,7 @@ export default defineComponent({
|
||||
Modal,
|
||||
Button,
|
||||
},
|
||||
emits: ['toggleModal', 'selectedInvoiceName'],
|
||||
emits: ['toggleModal'],
|
||||
methods: {
|
||||
routeTo,
|
||||
},
|
||||
|
443
src/pages/POS/ClassicPOS.vue
Normal file
443
src/pages/POS/ClassicPOS.vue
Normal file
@ -0,0 +1,443 @@
|
||||
<template>
|
||||
<div>
|
||||
<OpenPOSShiftModal
|
||||
v-if="!isPosShiftOpen"
|
||||
:open-modal="!isPosShiftOpen"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<ClosePOSShiftModal
|
||||
:open-modal="openShiftCloseModal"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<LoyaltyProgramModal
|
||||
:open-modal="openLoyaltyProgramModal"
|
||||
:loyalty-points="loyaltyPoints"
|
||||
:loyalty-program="loyaltyProgram"
|
||||
@set-loyalty-points="emitSetLoyaltyPoints"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
<SavedInvoiceModal
|
||||
:open-modal="openSavedInvoiceModal"
|
||||
:modal-status="openSavedInvoiceModal"
|
||||
@selected-invoice-name="emitSelectedInvoice"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<CouponCodeModal
|
||||
:open-modal="openCouponCodeModal"
|
||||
@toggle-modal="toggleModal"
|
||||
@set-coupons-count="emitCouponsCount"
|
||||
/>
|
||||
|
||||
<PaymentModal
|
||||
:open-modal="openPaymentModal"
|
||||
@toggle-modal="toggleModal"
|
||||
@set-cash-amount="emitSetCashAmount"
|
||||
@set-coupons-count="emitCouponsCount"
|
||||
@set-transfer-ref-no="setTransferRefNo"
|
||||
@set-transfer-amount="emitSetTransferAmount"
|
||||
@create-transaction="emitCreateTransaction"
|
||||
@set-transfer-clearance-date="setTransferClearanceDate"
|
||||
/>
|
||||
|
||||
<AlertModal :open-modal="openAlertModal" @toggle-modal="toggleModal" />
|
||||
|
||||
<div
|
||||
class="bg-gray-25 dark:bg-gray-875 grid grid-cols-12 gap-2 p-4"
|
||||
style="height: calc(100vh - var(--h-row-largest))"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
col-span-5
|
||||
bg-white
|
||||
border
|
||||
rounded-md
|
||||
dark:border-gray-800 dark:bg-gray-850
|
||||
"
|
||||
>
|
||||
<div class="rounded-md p-4 col-span-5">
|
||||
<div class="flex gap-x-2">
|
||||
<!-- Item Search -->
|
||||
<Link
|
||||
:class="
|
||||
fyo.singles.InventorySettings?.enableBarcodes
|
||||
? 'flex-shrink-0 w-2/3'
|
||||
: 'w-full'
|
||||
"
|
||||
:df="{
|
||||
label: t`Search an Item`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'item',
|
||||
target: 'Item',
|
||||
}"
|
||||
:border="true"
|
||||
:value="itemSearchTerm"
|
||||
@keyup.enter="
|
||||
async () => await selectItem(await getItem(itemSearchTerm))
|
||||
"
|
||||
@change="(item: string) =>itemSearchTerm= item"
|
||||
/>
|
||||
|
||||
<Barcode
|
||||
v-if="fyo.singles.InventorySettings?.enableBarcodes"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string) => {
|
||||
await selectItem(await getItem(name));
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ItemsTable
|
||||
v-if="tableView"
|
||||
:items="items"
|
||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||
@add-item="selectItem"
|
||||
/>
|
||||
|
||||
<ItemsGrid
|
||||
v-else
|
||||
:items="items"
|
||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||
@add-item="selectItem"
|
||||
/>
|
||||
|
||||
<div class="flex fixed bottom-0 p-1 mb-7 gap-x-3">
|
||||
<POSQuickActions
|
||||
:sinv-doc="sinvDoc"
|
||||
:loyalty-points="loyaltyPoints"
|
||||
:loyalty-program="loyaltyProgram"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
@toggle-view="toggleView"
|
||||
@toggle-modal="toggleModal"
|
||||
@emit-route-to-sinv-list="emitRouteToSinvList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-7">
|
||||
<div class="flex flex-col gap-3" style="height: calc(100vh - 6rem)">
|
||||
<div
|
||||
class="
|
||||
p-4
|
||||
bg-white
|
||||
border
|
||||
rounded-md
|
||||
grow
|
||||
h-full
|
||||
dark:border-gray-800 dark:bg-gray-850
|
||||
"
|
||||
>
|
||||
<!-- Customer Search -->
|
||||
<MultiLabelLink
|
||||
v-if="sinvDoc?.fieldMap"
|
||||
class="flex-shrink-0"
|
||||
secondary-link="phone"
|
||||
:border="true"
|
||||
:value="sinvDoc?.party"
|
||||
:df="sinvDoc?.fieldMap.party"
|
||||
@change="(value:string) => $emit('setCustomer',value)"
|
||||
/>
|
||||
|
||||
<SelectedItemTable />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
p-4
|
||||
bg-white
|
||||
border
|
||||
rounded-md
|
||||
dark:border-gray-800 dark:bg-gray-850
|
||||
"
|
||||
>
|
||||
<div class="w-full grid grid-cols-2 gap-y-2 gap-x-3">
|
||||
<div class="">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<FloatingLabelFloatInput
|
||||
:df="{
|
||||
label: t`Total Quantity`,
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'totalQuantity',
|
||||
minvalue: 0,
|
||||
maxvalue: 1000,
|
||||
}"
|
||||
size="large"
|
||||
:value="totalQuantity"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
|
||||
<FloatingLabelCurrencyInput
|
||||
:df="{
|
||||
label: t`Add'l Discounts`,
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'additionalDiscount',
|
||||
minvalue: 0,
|
||||
}"
|
||||
size="large"
|
||||
:value="additionalDiscounts"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
@change="(amount:Money)=> additionalDiscounts= amount"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<FloatingLabelCurrencyInput
|
||||
:df="{
|
||||
label: t`Item Discounts`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'itemDiscounts',
|
||||
}"
|
||||
size="large"
|
||||
:value="itemDiscounts"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
<FloatingLabelCurrencyInput
|
||||
v-if="sinvDoc?.fieldMap"
|
||||
:df="sinvDoc?.fieldMap.grandTotal"
|
||||
size="large"
|
||||
:value="sinvDoc?.grandTotal"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<Button
|
||||
class="w-full bg-violet-500 dark:bg-violet-700 py-6"
|
||||
:disabled="!sinvDoc?.party || !sinvDoc?.items?.length"
|
||||
@click="$emit('saveInvoiceAction')"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Save` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
<Button
|
||||
class="w-full mt-4 bg-blue-500 dark:bg-blue-700 py-6"
|
||||
@click="toggleModal('SavedInvoice', true)"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`held` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Button
|
||||
class="w-full bg-red-500 dark:bg-red-700 py-6"
|
||||
:disabled="!sinvDoc?.items?.length"
|
||||
@click="() => $emit('clearValues')"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Cancel` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="mt-4 w-full bg-green-500 dark:bg-green-700 py-6"
|
||||
:disabled="disablePayButton"
|
||||
@click="toggleModal('Payment', true)"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Pay` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Money } from 'pesa';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { getItem } from 'src/utils/pos';
|
||||
import AlertModal from './AlertModal.vue';
|
||||
import PaymentModal from './PaymentModal.vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import CouponCodeModal from './CouponCodeModal.vue';
|
||||
import POSQuickActions from './POSQuickActions.vue';
|
||||
import { ModalName } from 'src/components/POS/types';
|
||||
import SavedInvoiceModal from './SavedInvoiceModal.vue';
|
||||
import OpenPOSShiftModal from './OpenPOSShiftModal.vue';
|
||||
import ClosePOSShiftModal from './ClosePOSShiftModal.vue';
|
||||
import Barcode from 'src/components/Controls/Barcode.vue';
|
||||
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||
import LoyaltyProgramModal from './LoyaltyProgramModal.vue';
|
||||
import ItemsGrid from 'src/components/POS/Classic/ItemsGrid.vue';
|
||||
import ItemsTable from 'src/components/POS/Classic/ItemsTable.vue';
|
||||
import MultiLabelLink from 'src/components/Controls/MultiLabelLink.vue';
|
||||
import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import SelectedItemTable from 'src/components/POS/Classic/SelectedItemTable.vue';
|
||||
import FloatingLabelFloatInput from 'src/components/POS/FloatingLabelFloatInput.vue';
|
||||
import FloatingLabelCurrencyInput from 'src/components/POS/FloatingLabelCurrencyInput.vue';
|
||||
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
import {
|
||||
ItemQtyMap,
|
||||
ItemSerialNumbers,
|
||||
POSItem,
|
||||
} from 'src/components/POS/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ClassicPOS',
|
||||
components: {
|
||||
Link,
|
||||
Button,
|
||||
Barcode,
|
||||
ItemsGrid,
|
||||
AlertModal,
|
||||
ItemsTable,
|
||||
PaymentModal,
|
||||
MultiLabelLink,
|
||||
CouponCodeModal,
|
||||
POSQuickActions,
|
||||
OpenPOSShiftModal,
|
||||
SavedInvoiceModal,
|
||||
SelectedItemTable,
|
||||
ClosePOSShiftModal,
|
||||
LoyaltyProgramModal,
|
||||
FloatingLabelFloatInput,
|
||||
FloatingLabelCurrencyInput,
|
||||
},
|
||||
props: {
|
||||
cashAmount: Money,
|
||||
itemDiscounts: Money,
|
||||
openAlertModal: Boolean,
|
||||
disablePayButton: Boolean,
|
||||
openPaymentModal: Boolean,
|
||||
openCouponCodeModal: Boolean,
|
||||
openShiftCloseModal: Boolean,
|
||||
openSavedInvoiceModal: Boolean,
|
||||
openLoyaltyProgramModal: Boolean,
|
||||
openAppliedCouponsModal: Boolean,
|
||||
loyaltyPoints: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
loyaltyProgram: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
appliedCouponsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
sinvDoc: {
|
||||
type: Object as PropType<SalesInvoice | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
itemQuantityMap: {
|
||||
type: Object as PropType<ItemQtyMap>,
|
||||
default: () => ({}),
|
||||
},
|
||||
coupons: {
|
||||
type: Object as PropType<AppliedCouponCodes>,
|
||||
default: () => ({}),
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<POSItem[] | undefined>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'addItem',
|
||||
'toggleModal',
|
||||
'setCustomer',
|
||||
'clearValues',
|
||||
'setCashAmount',
|
||||
'setCouponsCount',
|
||||
'routeToSinvList',
|
||||
'setLoyaltyPoints',
|
||||
'saveInvoiceAction',
|
||||
'createTransaction',
|
||||
'setTransferAmount',
|
||||
'selectedInvoiceName',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
tableView: true,
|
||||
|
||||
totalQuantity: 0,
|
||||
totalTaxedAmount: fyo.pesa(0),
|
||||
additionalDiscounts: fyo.pesa(0),
|
||||
|
||||
paymentDoc: {} as Payment,
|
||||
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||
|
||||
itemSearchTerm: '',
|
||||
transferRefNo: undefined as string | undefined,
|
||||
transferClearanceDate: undefined as Date | undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPosShiftOpen: () => !!fyo.singles.POSShift?.isShiftOpen,
|
||||
},
|
||||
methods: {
|
||||
setTransferRefNo(ref: string) {
|
||||
this.transferRefNo = ref;
|
||||
},
|
||||
emitRouteToSinvList() {
|
||||
this.$emit('routeToSinvList');
|
||||
},
|
||||
toggleView() {
|
||||
this.tableView = !this.tableView;
|
||||
},
|
||||
emitSetCashAmount(amount: Money) {
|
||||
this.$emit('setCashAmount', amount);
|
||||
},
|
||||
setTransferClearanceDate(date: Date) {
|
||||
this.transferClearanceDate = date;
|
||||
},
|
||||
emitCouponsCount(value: number) {
|
||||
this.$emit('setCouponsCount', value);
|
||||
},
|
||||
emitSetLoyaltyPoints(value: string) {
|
||||
this.$emit('setLoyaltyPoints', value);
|
||||
},
|
||||
emitSelectedInvoice(doc: InvoiceItem) {
|
||||
this.$emit('selectedInvoiceName', doc);
|
||||
},
|
||||
toggleModal(modal: ModalName, value: boolean) {
|
||||
this.$emit('toggleModal', modal, value);
|
||||
},
|
||||
emitCreateTransaction(shouldPrint = false) {
|
||||
this.$emit('createTransaction', shouldPrint);
|
||||
},
|
||||
emitSetTransferAmount(amount: Money = fyo.pesa(0)) {
|
||||
this.$emit('setTransferAmount', amount);
|
||||
},
|
||||
selectItem(item: POSItem | Item | undefined) {
|
||||
this.$emit('addItem', item);
|
||||
},
|
||||
openCouponModal() {
|
||||
if (this.sinvDoc?.party && this.sinvDoc?.items?.length) {
|
||||
this.toggleModal('CouponCode', true);
|
||||
}
|
||||
},
|
||||
getItem,
|
||||
},
|
||||
});
|
||||
</script>
|
494
src/pages/POS/KeyboardModal.vue
Normal file
494
src/pages/POS/KeyboardModal.vue
Normal file
@ -0,0 +1,494 @@
|
||||
<template>
|
||||
<Modal class="h-auto" :set-close-listener="false">
|
||||
<div class="px-5" style="width: 30vw">
|
||||
<p class="text-center font-semibold py-3">Keyboard</p>
|
||||
<hr class="dark:border-gray-800" />
|
||||
<div class="mx-6 my-3">
|
||||
<component
|
||||
:is="selectedItemRow?.fieldMap[selectedItemField!].fieldtype"
|
||||
ref="dynamicInput"
|
||||
:df="{
|
||||
fieldname: selectedItemRow?.fieldMap[selectedItemField!].fieldname as string,
|
||||
fieldtype: selectedItemRow?.fieldMap[selectedItemField!].fieldtype,
|
||||
label: selectedItemRow?.fieldMap[selectedItemField!].label as string,
|
||||
}"
|
||||
class="mb-3"
|
||||
:border="true"
|
||||
:show-label="true"
|
||||
:value="selectedValue"
|
||||
:focus-input="true"
|
||||
@change="(value: number) => handleInput(value.toString())"
|
||||
/>
|
||||
|
||||
<div
|
||||
id="keypad"
|
||||
class="text-4xl grid grid-cols-4 gap-3 rounded font-bold py-4"
|
||||
>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('7')"
|
||||
>
|
||||
7
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('8')"
|
||||
>
|
||||
8
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('9')"
|
||||
>
|
||||
9
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="deleteLast()"
|
||||
>
|
||||
Del
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('4')"
|
||||
>
|
||||
4
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('5')"
|
||||
>
|
||||
5
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('6')"
|
||||
>
|
||||
6
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('-')"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('1')"
|
||||
>
|
||||
1
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('2')"
|
||||
>
|
||||
2
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('3')"
|
||||
>
|
||||
3
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('+')"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('.')"
|
||||
>
|
||||
•
|
||||
</button>
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="appendValue('0')"
|
||||
>
|
||||
0
|
||||
</button>
|
||||
<div class="grid col-span-2">
|
||||
<button
|
||||
class="
|
||||
py-2.5
|
||||
bg-gray-100
|
||||
text-2xl
|
||||
border-transparent
|
||||
rounded-lg
|
||||
transition-colors
|
||||
duration-200
|
||||
hover:bg-gray-200
|
||||
dark:bg-gray-875 dark:hover:bg-gray-900
|
||||
"
|
||||
@click="reset()"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5">
|
||||
<div class="grid row-start-6 grid-cols-2 gap-4 mt-auto mb-3">
|
||||
<div class="col-span-2">
|
||||
<Button
|
||||
class="w-full bg-green-500 dark:bg-green-700"
|
||||
style="padding: 1.35rem"
|
||||
@click="saveSelectedItem()"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Save` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid row-start-6 grid-cols-2 gap-4 mt-auto mb-8">
|
||||
<div class="col-span-2">
|
||||
<Button
|
||||
class="w-full bg-red-500 dark:bg-red-700"
|
||||
style="padding: 1.35rem"
|
||||
@click="closeKeyboardModal()"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Cancel` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Modal from 'src/components/Modal.vue';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import Float from 'src/components/Controls/Float.vue';
|
||||
import Currency from 'src/components/Controls/Currency.vue';
|
||||
import { updatePricingRuleItem } from 'models/helpers';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'KeyboardModal',
|
||||
components: {
|
||||
Modal,
|
||||
Float,
|
||||
Button,
|
||||
Currency,
|
||||
},
|
||||
props: {
|
||||
modalStatus: Boolean,
|
||||
selectedItemRow: SalesInvoiceItem,
|
||||
selectedItemField: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['toggleModal'],
|
||||
setup() {
|
||||
return {
|
||||
sinvDoc: inject('sinvDoc') as SalesInvoice,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedValue: '',
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
async modalStatus(newVal) {
|
||||
if (newVal) {
|
||||
await this.$nextTick();
|
||||
await this.focusInput();
|
||||
}
|
||||
this.updateSelectedValue();
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.updateSelectedValue();
|
||||
await this.focusInput();
|
||||
},
|
||||
methods: {
|
||||
async appendValue(value: string) {
|
||||
if (value === '-') {
|
||||
this.selectedValue = this.selectedValue.startsWith('-')
|
||||
? this.selectedValue
|
||||
: `-${this.selectedValue}`;
|
||||
} else if (value === '+') {
|
||||
this.selectedValue = this.selectedValue.startsWith('-')
|
||||
? this.selectedValue.slice(1)
|
||||
: this.selectedValue;
|
||||
} else {
|
||||
this.selectedValue =
|
||||
this.selectedValue === '0' ? value : this.selectedValue + value;
|
||||
}
|
||||
|
||||
await this.focusInput();
|
||||
},
|
||||
updateSelectedValue() {
|
||||
this.selectedValue = '';
|
||||
|
||||
if (
|
||||
this.selectedItemRow?.fieldMap[this.selectedItemField].fieldtype !==
|
||||
ModelNameEnum.Currency
|
||||
) {
|
||||
this.selectedValue = this.selectedItemRow![
|
||||
this.selectedItemField
|
||||
] as string;
|
||||
}
|
||||
},
|
||||
handleInput(value: string) {
|
||||
this.selectedValue = value;
|
||||
},
|
||||
async saveSelectedItem() {
|
||||
try {
|
||||
if (
|
||||
this.selectedItemRow?.fieldMap[this.selectedItemField].fieldtype ===
|
||||
ModelNameEnum.Currency
|
||||
) {
|
||||
this.selectedItemRow[this.selectedItemField] = this.fyo.pesa(
|
||||
Number(this.selectedValue)
|
||||
);
|
||||
|
||||
if (this.selectedItemField === 'rate') {
|
||||
this.selectedItemRow.setRate = this.fyo.pesa(
|
||||
Number(this.selectedValue)
|
||||
);
|
||||
|
||||
await this.sinvDoc.runFormulas();
|
||||
this.$emit('toggleModal', 'Keyboard');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedItemField === 'itemDiscountAmount') {
|
||||
if (this.sinvDoc.grandTotal?.lte(this.selectedValue)) {
|
||||
this.selectedItemRow.itemDiscountAmount = this.fyo.pesa(
|
||||
Number(0)
|
||||
);
|
||||
|
||||
throw new ValidationError(
|
||||
this.fyo.t`Discount Amount (${this.fyo.format(
|
||||
this.selectedValue,
|
||||
'Currency'
|
||||
)}) cannot be greated than Amount (${this.fyo.format(
|
||||
this.sinvDoc.grandTotal,
|
||||
'Currency'
|
||||
)}).`
|
||||
);
|
||||
}
|
||||
|
||||
this.selectedItemRow.setItemDiscountAmount = true;
|
||||
this.selectedItemRow.itemDiscountAmount = this.fyo.pesa(
|
||||
Number(this.selectedValue)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.selectedItemRow![this.selectedItemField] = Number(
|
||||
this.selectedValue
|
||||
);
|
||||
|
||||
if (this.selectedItemField === 'itemDiscountPercent') {
|
||||
if (Number(this.selectedValue) > 100) {
|
||||
await this.selectedItemRow?.set('itemDiscountPercent', 0);
|
||||
|
||||
throw new ValidationError(
|
||||
this.fyo
|
||||
.t`Discount Percent (${this.selectedValue}) cannot be greater than 100.`
|
||||
);
|
||||
}
|
||||
|
||||
await this.selectedItemRow?.set('setItemDiscountAmount', false);
|
||||
await this.selectedItemRow?.set(
|
||||
'itemDiscountPercent',
|
||||
this.selectedValue
|
||||
);
|
||||
}
|
||||
|
||||
if (this.selectedItemField === 'quantity') {
|
||||
await updatePricingRuleItem(this.sinvDoc);
|
||||
}
|
||||
}
|
||||
|
||||
await this.sinvDoc.runFormulas();
|
||||
this.$emit('toggleModal', 'Keyboard');
|
||||
} catch (error) {
|
||||
showToast({
|
||||
type: 'error',
|
||||
message: this.t`${error as string}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
async deleteLast() {
|
||||
this.selectedValue = this.selectedValue?.slice(0, -1);
|
||||
await this.focusInput();
|
||||
},
|
||||
async reset() {
|
||||
this.selectedValue = '';
|
||||
await this.focusInput();
|
||||
},
|
||||
async focusInput() {
|
||||
await this.$nextTick();
|
||||
(this.$refs.dynamicInput as HTMLInputElement)?.focus();
|
||||
},
|
||||
async closeKeyboardModal() {
|
||||
await this.reset();
|
||||
this.$emit('toggleModal', 'Keyboard');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -107,6 +107,15 @@ export default defineComponent({
|
||||
methods: {
|
||||
async updateLoyaltyPoints(newValue: number) {
|
||||
try {
|
||||
const partyData = await this.fyo.db.get(
|
||||
ModelNameEnum.Party,
|
||||
this.sinvDoc.party as string
|
||||
);
|
||||
|
||||
if (!partyData.loyaltyProgram) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.loyaltyPoints >= newValue) {
|
||||
this.sinvDoc.loyaltyPoints = newValue;
|
||||
} else {
|
||||
@ -121,7 +130,7 @@ export default defineComponent({
|
||||
ModelNameEnum.LoyaltyProgram,
|
||||
{
|
||||
fields: ['conversionFactor'],
|
||||
filters: { name: this.loyaltyProgram },
|
||||
filters: { name: partyData.loyaltyProgram as string },
|
||||
}
|
||||
);
|
||||
|
462
src/pages/POS/ModernPOS.vue
Normal file
462
src/pages/POS/ModernPOS.vue
Normal file
@ -0,0 +1,462 @@
|
||||
<template>
|
||||
<div>
|
||||
<OpenPOSShiftModal
|
||||
v-if="!isPosShiftOpen"
|
||||
:open-modal="!isPosShiftOpen"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<ClosePOSShiftModal
|
||||
:open-modal="openShiftCloseModal"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<LoyaltyProgramModal
|
||||
:open-modal="openLoyaltyProgramModal"
|
||||
:loyalty-points="loyaltyPoints"
|
||||
:loyalty-program="loyaltyProgram"
|
||||
@set-loyalty-points="emitSetLoyaltyPoints"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<SavedInvoiceModal
|
||||
:open-modal="openSavedInvoiceModal"
|
||||
:modal-status="openSavedInvoiceModal"
|
||||
@toggle-modal="toggleModal"
|
||||
@selected-invoice-name="emitSelectedInvoice"
|
||||
/>
|
||||
|
||||
<CouponCodeModal
|
||||
:open-modal="openCouponCodeModal"
|
||||
@toggle-modal="toggleModal"
|
||||
@set-coupons-count="emitCouponsCount"
|
||||
/>
|
||||
|
||||
<PaymentModal
|
||||
:open-modal="openPaymentModal"
|
||||
@toggle-modal="toggleModal"
|
||||
@set-cash-amount="emitSetCashAmount"
|
||||
@set-coupons-count="emitCouponsCount"
|
||||
@set-transfer-ref-no="setTransferRefNo"
|
||||
@create-transaction="emitCreateTransaction"
|
||||
@set-transfer-amount="emitSetTransferAmount"
|
||||
@set-transfer-clearance-date="setTransferClearanceDate"
|
||||
/>
|
||||
|
||||
<AlertModal :open-modal="openAlertModal" @toggle-modal="toggleModal" />
|
||||
|
||||
<KeyboardModal
|
||||
v-if="selectedItemField && selectedItemRow"
|
||||
:open-modal="openKeyboardModal"
|
||||
:modal-status="openKeyboardModal"
|
||||
:selected-item-field="selectedItemField"
|
||||
:selected-item-row="(selectedItemRow as SalesInvoiceItem)"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<div class="bg-gray-25 dark:bg-gray-875 grid grid-cols-9 gap-3 p-4">
|
||||
<div class="col-span-3 flex h-auto w-full">
|
||||
<div class="grid grid-rows-5 w-full gap-3">
|
||||
<div
|
||||
class="
|
||||
p-4
|
||||
grow
|
||||
h-full
|
||||
row-span-5
|
||||
bg-white
|
||||
border
|
||||
rounded-md
|
||||
dark:bg-gray-850 dark:border-gray-800
|
||||
"
|
||||
>
|
||||
<!-- Customer Search -->
|
||||
<MultiLabelLink
|
||||
v-if="sinvDoc?.fieldMap"
|
||||
class="flex-shrink-0"
|
||||
secondary-link="phone"
|
||||
:border="true"
|
||||
:value="sinvDoc?.party"
|
||||
:df="sinvDoc?.fieldMap.party"
|
||||
@change="(value:string) => $emit('setCustomer',value)"
|
||||
/>
|
||||
<ModernPOSSelectedItemTable
|
||||
@selected-row="selectedRow"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
p-4
|
||||
bg-white
|
||||
border
|
||||
rounded-md
|
||||
dark:bg-gray-850 dark:border-gray-800
|
||||
"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<FloatingLabelFloatInput
|
||||
:df="{
|
||||
label: t`Total Quantity`,
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'totalQuantity',
|
||||
minvalue: 0,
|
||||
maxvalue: 1000,
|
||||
}"
|
||||
size="large"
|
||||
:value="totalQuantity"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
|
||||
<FloatingLabelCurrencyInput
|
||||
:df="{
|
||||
label: t`Add'l Discounts`,
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'additionalDiscount',
|
||||
minvalue: 0,
|
||||
}"
|
||||
size="large"
|
||||
:value="additionalDiscounts"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
@change="(amount:Money)=> additionalDiscounts = amount"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 grid grid-cols-2 gap-2">
|
||||
<FloatingLabelCurrencyInput
|
||||
:df="{
|
||||
label: t`Item Discounts`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'itemDiscounts',
|
||||
}"
|
||||
size="large"
|
||||
:value="itemDiscounts"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
<FloatingLabelCurrencyInput
|
||||
v-if="sinvDoc?.fieldMap"
|
||||
:df="sinvDoc?.fieldMap.grandTotal"
|
||||
size="large"
|
||||
:value="sinvDoc?.grandTotal"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<Button
|
||||
class="mt-2 w-full bg-violet-500 dark:bg-violet-700 py-5"
|
||||
:disabled="!sinvDoc?.party || !sinvDoc?.items?.length"
|
||||
@click="$emit('saveInvoiceAction')"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Save` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
<Button
|
||||
class="w-full mt-2 bg-blue-500 dark:bg-blue-700 py-5"
|
||||
@click="toggleModal('SavedInvoice', true)"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`held` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Button
|
||||
class="mt-2 w-full bg-red-500 dark:bg-red-700 py-5"
|
||||
:disabled="!sinvDoc?.items?.length"
|
||||
@click="() => $emit('clearValues')"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Cancel` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="mt-2 w-full bg-green-500 dark:bg-green-700 py-5"
|
||||
:disabled="disablePayButton"
|
||||
@click="toggleModal('Payment', true)"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Pay` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
bg-white
|
||||
border
|
||||
rounded-md
|
||||
col-span-6
|
||||
flex flex-col
|
||||
dark:bg-gray-850 dark:border-gray-800
|
||||
"
|
||||
style="height: calc(100vh - 6rem)"
|
||||
>
|
||||
<div class="rounded-md p-4 col-span-5 h-full">
|
||||
<div class="flex gap-x-2">
|
||||
<!-- Item Search -->
|
||||
<Link
|
||||
:class="
|
||||
fyo.singles.InventorySettings?.enableBarcodes
|
||||
? 'flex-shrink-0 w-2/3'
|
||||
: 'w-full'
|
||||
"
|
||||
:df="{
|
||||
label: t`Search an Item`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'item',
|
||||
target: 'Item',
|
||||
}"
|
||||
:border="true"
|
||||
:value="itemSearchTerm"
|
||||
@keyup.enter="
|
||||
async () => await selectItem(await getItem(itemSearchTerm))
|
||||
"
|
||||
@change="(item: string) =>itemSearchTerm= item"
|
||||
/>
|
||||
|
||||
<Barcode
|
||||
v-if="fyo.singles.InventorySettings?.enableBarcodes"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string) => {
|
||||
await selectItem(await getItem(name));
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<ModernPOSItemsTable
|
||||
v-if="tableView"
|
||||
:items="items"
|
||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||
@add-item="selectItem"
|
||||
/>
|
||||
<ModernPOSItemsGrid
|
||||
v-else
|
||||
:items="items"
|
||||
:item-qty-map="itemQuantityMap as ItemQtyMap"
|
||||
@add-item="selectItem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex fixed bottom-0 p-1 ml-3 mb-7 gap-x-3">
|
||||
<POSQuickActions
|
||||
:sinv-doc="sinvDoc"
|
||||
:loyalty-points="loyaltyPoints"
|
||||
:loyalty-program="loyaltyProgram"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
@toggle-view="toggleView"
|
||||
@toggle-modal="toggleModal"
|
||||
@emit-route-to-sinv-list="emitRouteToSinvList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Money } from 'pesa';
|
||||
import { PropType } from 'vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { defineComponent } from 'vue';
|
||||
import { getItem } from 'src/utils/pos';
|
||||
import AlertModal from './AlertModal.vue';
|
||||
import PaymentModal from './PaymentModal.vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import KeyboardModal from './KeyboardModal.vue';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import CouponCodeModal from './CouponCodeModal.vue';
|
||||
import POSQuickActions from './POSQuickActions.vue';
|
||||
import OpenPOSShiftModal from './OpenPOSShiftModal.vue';
|
||||
import SavedInvoiceModal from './SavedInvoiceModal.vue';
|
||||
import Barcode from 'src/components/Controls/Barcode.vue';
|
||||
import ClosePOSShiftModal from './ClosePOSShiftModal.vue';
|
||||
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||
import LoyaltyProgramModal from './LoyaltyProgramModal.vue';
|
||||
import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem';
|
||||
import MultiLabelLink from 'src/components/Controls/MultiLabelLink.vue';
|
||||
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 FloatingLabelFloatInput from 'src/components/POS/FloatingLabelFloatInput.vue';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import FloatingLabelCurrencyInput from 'src/components/POS/FloatingLabelCurrencyInput.vue';
|
||||
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
import ModernPOSSelectedItemTable from 'src/components/POS/Modern/ModernPOSSelectedItemTable.vue';
|
||||
import {
|
||||
ItemQtyMap,
|
||||
ItemSerialNumbers,
|
||||
ModalName,
|
||||
POSItem,
|
||||
} from 'src/components/POS/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModernPos',
|
||||
components: {
|
||||
Link,
|
||||
Button,
|
||||
Barcode,
|
||||
AlertModal,
|
||||
PaymentModal,
|
||||
KeyboardModal,
|
||||
MultiLabelLink,
|
||||
POSQuickActions,
|
||||
CouponCodeModal,
|
||||
OpenPOSShiftModal,
|
||||
SavedInvoiceModal,
|
||||
ModernPOSItemsGrid,
|
||||
ClosePOSShiftModal,
|
||||
LoyaltyProgramModal,
|
||||
ModernPOSItemsTable,
|
||||
FloatingLabelFloatInput,
|
||||
FloatingLabelCurrencyInput,
|
||||
ModernPOSSelectedItemTable,
|
||||
},
|
||||
props: {
|
||||
cashAmount: Money,
|
||||
itemDiscounts: Money,
|
||||
openAlertModal: Boolean,
|
||||
disablePayButton: Boolean,
|
||||
openPaymentModal: Boolean,
|
||||
openKeyboardModal: Boolean,
|
||||
openCouponCodeModal: Boolean,
|
||||
openShiftCloseModal: Boolean,
|
||||
openSavedInvoiceModal: Boolean,
|
||||
openLoyaltyProgramModal: Boolean,
|
||||
openAppliedCouponsModal: Boolean,
|
||||
loyaltyPoints: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
loyaltyProgram: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
appliedCouponsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
coupons: {
|
||||
type: Object as PropType<AppliedCouponCodes>,
|
||||
default: () => ({}),
|
||||
},
|
||||
sinvDoc: {
|
||||
type: Object as PropType<SalesInvoice | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
itemQuantityMap: {
|
||||
type: Object as PropType<ItemQtyMap>,
|
||||
default: () => ({}),
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<POSItem[] | undefined>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'addItem',
|
||||
'toggleModal',
|
||||
'setCustomer',
|
||||
'clearValues',
|
||||
'setCashAmount',
|
||||
'setCouponsCount',
|
||||
'routeToSinvList',
|
||||
'setLoyaltyPoints',
|
||||
'saveInvoiceAction',
|
||||
'createTransaction',
|
||||
'setTransferAmount',
|
||||
'selectedInvoiceName',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
tableView: true,
|
||||
|
||||
totalQuantity: 0,
|
||||
totalTaxedAmount: fyo.pesa(0),
|
||||
additionalDiscounts: fyo.pesa(0),
|
||||
|
||||
paymentDoc: {} as Payment,
|
||||
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||
|
||||
selectedItemField: '',
|
||||
selectedItemRow: {} as SalesInvoiceItem,
|
||||
|
||||
itemSearchTerm: '',
|
||||
transferRefNo: undefined as string | undefined,
|
||||
transferClearanceDate: undefined as Date | undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPosShiftOpen: () => !!fyo.singles.POSShift?.isShiftOpen,
|
||||
},
|
||||
methods: {
|
||||
setTransferRefNo(ref: string) {
|
||||
this.transferRefNo = ref;
|
||||
},
|
||||
toggleView() {
|
||||
this.tableView = !this.tableView;
|
||||
},
|
||||
emitSetCashAmount(amount: Money) {
|
||||
this.$emit('setCashAmount', amount);
|
||||
},
|
||||
setTransferClearanceDate(date: Date) {
|
||||
this.transferClearanceDate = date;
|
||||
},
|
||||
emitCouponsCount(value: number) {
|
||||
this.$emit('setCouponsCount', value);
|
||||
},
|
||||
emitRouteToSinvList() {
|
||||
this.$emit('routeToSinvList');
|
||||
},
|
||||
emitSetLoyaltyPoints(value: string) {
|
||||
this.$emit('setLoyaltyPoints', value);
|
||||
},
|
||||
emitSelectedInvoice(doc: InvoiceItem) {
|
||||
this.$emit('selectedInvoiceName', doc);
|
||||
},
|
||||
toggleModal(modal: ModalName, value: boolean) {
|
||||
this.$emit('toggleModal', modal, value);
|
||||
},
|
||||
emitCreateTransaction(shouldPrint = false) {
|
||||
this.$emit('createTransaction', shouldPrint);
|
||||
},
|
||||
selectedRow(row: SalesInvoiceItem, field: string) {
|
||||
this.selectedItemRow = row;
|
||||
this.selectedItemField = field;
|
||||
},
|
||||
emitSetTransferAmount(amount: Money = fyo.pesa(0)) {
|
||||
this.$emit('setTransferAmount', amount);
|
||||
},
|
||||
selectItem(item: POSItem | Item | undefined) {
|
||||
this.$emit('addItem', item);
|
||||
},
|
||||
openCouponModal() {
|
||||
if (this.sinvDoc?.party && this.sinvDoc?.items?.length) {
|
||||
this.toggleModal('CouponCode', true);
|
||||
}
|
||||
},
|
||||
getItem,
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="flex-col">
|
||||
<PageHeader :title="t`Point of Sale`">
|
||||
<slot>
|
||||
<Button
|
||||
@ -10,646 +10,179 @@
|
||||
</Button>
|
||||
</slot>
|
||||
</PageHeader>
|
||||
|
||||
<OpenPOSShiftModal
|
||||
v-if="!isPosShiftOpen"
|
||||
:open-modal="!isPosShiftOpen"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<ClosePOSShiftModal
|
||||
:open-modal="openShiftCloseModal"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<LoyaltyProgramModal
|
||||
:open-modal="openLoyaltyProgramModal"
|
||||
<ClassicPOS
|
||||
v-if="fyo.singles.POSSettings?.posUI == 'Classic'"
|
||||
:item-quantity-qap="itemQtyMap"
|
||||
:loyalty-points="loyaltyPoints"
|
||||
:loyalty-program="loyaltyProgram"
|
||||
@set-loyalty-points="setLoyaltyPoints"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
<SavedInvoiceModal
|
||||
:open-modal="openSavedInvoiceModal"
|
||||
:modal-status="openSavedInvoiceModal"
|
||||
@selected-invoice-name="selectedInvoiceName"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<CouponCodeModal
|
||||
:open-modal="openCouponCodeModal"
|
||||
@set-coupons-count="setCouponsCount"
|
||||
@toggle-modal="toggleModal"
|
||||
/>
|
||||
|
||||
<PaymentModal
|
||||
:open-modal="openPaymentModal"
|
||||
@create-transaction="createTransaction"
|
||||
:open-alert-modal="openAlertModal"
|
||||
:default-customer="defaultCustomer"
|
||||
:items="(items as [] as POSItem[])"
|
||||
:cash-amount="(cashAmount as Money)"
|
||||
:sinv-doc="(sinvDoc as SalesInvoice)"
|
||||
:disable-pay-button="disablePayButton"
|
||||
:open-payment-modal="openPaymentModal"
|
||||
:item-discounts="(itemDiscounts as Money)"
|
||||
:coupons="(coupons as AppliedCouponCodes)"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
:open-shift-close-modal="openShiftCloseModal"
|
||||
:open-coupon-code-modal="openCouponCodeModal"
|
||||
:open-saved-invoice-modal="openSavedInvoiceModal"
|
||||
:open-loyalty-program-modal="openLoyaltyProgramModal"
|
||||
:open-applied-coupons-modal="openAppliedCouponsModal"
|
||||
@add-item="addItem"
|
||||
@set-sinv-doc="setSinvDoc"
|
||||
@clear-values="clearValues"
|
||||
@set-customer="setCustomer"
|
||||
@toggle-modal="toggleModal"
|
||||
@set-cash-amount="setCashAmount"
|
||||
@set-transfer-amount="setTransferAmount"
|
||||
@set-transfer-ref-no="setTransferRefNo"
|
||||
@set-coupons-count="setCouponsCount"
|
||||
@set-transfer-clearance-date="setTransferClearanceDate"
|
||||
@route-to-sinv-list="routeToSinvList"
|
||||
@set-loyalty-points="setLoyaltyPoints"
|
||||
@create-transaction="createTransaction"
|
||||
@save-invoice-action="saveInvoiceAction"
|
||||
@set-transfer-amount="setTransferAmount"
|
||||
@selected-invoice-name="selectedInvoiceName"
|
||||
/>
|
||||
|
||||
<AlertModal
|
||||
:open-modal="openRouteToInvoiceListModal"
|
||||
<ModernPOS
|
||||
v-else
|
||||
:item-quantity-qap="itemQtyMap"
|
||||
:loyalty-points="loyaltyPoints"
|
||||
:open-alert-modal="openAlertModal"
|
||||
:default-customer="defaultCustomer"
|
||||
:items="(items as [] as POSItem[])"
|
||||
:cash-amount="(cashAmount as Money)"
|
||||
:sinv-doc="(sinvDoc as SalesInvoice)"
|
||||
:disable-pay-button="disablePayButton"
|
||||
:open-payment-modal="openPaymentModal"
|
||||
:open-keyboard-modal="openKeyboardModal"
|
||||
:item-discounts="(itemDiscounts as Money)"
|
||||
:coupons="(coupons as AppliedCouponCodes)"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
:open-shift-close-modal="openShiftCloseModal"
|
||||
:open-coupon-code-modal="openCouponCodeModal"
|
||||
:open-saved-invoice-modal="openSavedInvoiceModal"
|
||||
:open-loyalty-program-modal="openLoyaltyProgramModal"
|
||||
:open-applied-coupons-modal="openAppliedCouponsModal"
|
||||
@add-item="addItem"
|
||||
@set-sinv-doc="setSinvDoc"
|
||||
@clear-values="clearValues"
|
||||
@set-customer="setCustomer"
|
||||
@toggle-modal="toggleModal"
|
||||
@set-cash-amount="setCashAmount"
|
||||
@set-coupons-count="setCouponsCount"
|
||||
@route-to-sinv-list="routeToSinvList"
|
||||
@set-loyalty-points="setLoyaltyPoints"
|
||||
@create-transaction="createTransaction"
|
||||
@save-invoice-action="saveInvoiceAction"
|
||||
@set-transfer-amount="setTransferAmount"
|
||||
@selected-invoice-name="selectedInvoiceName"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="bg-gray-25 dark:bg-gray-875 gap-2 grid grid-cols-12 p-4"
|
||||
style="height: calc(100vh - var(--h-row-largest))"
|
||||
>
|
||||
<div
|
||||
class="
|
||||
bg-white
|
||||
dark:bg-gray-850
|
||||
border
|
||||
dark:border-gray-800
|
||||
col-span-5
|
||||
rounded-md
|
||||
"
|
||||
>
|
||||
<div class="rounded-md p-4 col-span-5">
|
||||
<div class="flex gap-x-2">
|
||||
<!-- Item Search -->
|
||||
<Link
|
||||
:class="
|
||||
fyo.singles.InventorySettings?.enableBarcodes
|
||||
? 'flex-shrink-0 w-2/3'
|
||||
: 'w-full'
|
||||
"
|
||||
:df="{
|
||||
label: t`Search an Item`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'item',
|
||||
target: 'Item',
|
||||
}"
|
||||
:border="true"
|
||||
:value="itemSearchTerm"
|
||||
@keyup.enter="
|
||||
async () => await addItem(await getItem(itemSearchTerm))
|
||||
"
|
||||
@change="(item: string) =>itemSearchTerm= item"
|
||||
/>
|
||||
|
||||
<Barcode
|
||||
v-if="fyo.singles.InventorySettings?.enableBarcodes"
|
||||
class="w-1/3"
|
||||
@item-selected="
|
||||
async (name: string) => {
|
||||
await addItem(await getItem(name));
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ItemsTable
|
||||
v-if="tableView"
|
||||
:items="items"
|
||||
:item-qty-map="itemQtyMap"
|
||||
@add-item="addItem"
|
||||
/>
|
||||
|
||||
<ItemsGrid
|
||||
v-else
|
||||
:items="items"
|
||||
:item-qty-map="itemQtyMap"
|
||||
@add-item="addItem"
|
||||
/>
|
||||
|
||||
<div class="flex fixed bottom-0 p-1 mb-7 gap-x-3">
|
||||
<div class="relative group">
|
||||
<div class="bg-gray-100 p-1.5 rounded-md" @click="toggleView">
|
||||
<FeatherIcon
|
||||
:name="tableView ? 'grid' : 'list'"
|
||||
class="w-5 h-5 text-black"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
mb-2
|
||||
bg-gray-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
text-black text-xs
|
||||
rounded-md
|
||||
p-2
|
||||
w-20
|
||||
text-center
|
||||
opacity-0
|
||||
group-hover:opacity-100
|
||||
transition-opacity
|
||||
duration-300
|
||||
"
|
||||
>
|
||||
{{ tableView ? 'Grid View' : 'List View' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="px-1.5 py-1 rounded-md bg-gray-100"
|
||||
@click="routeToSinvList"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
width="21"
|
||||
fill="#000"
|
||||
>
|
||||
<path
|
||||
d="M240-100q-41.92 0-70.96-29.04Q140-158.08 140-199.82V-300h120v-552.31l55.39 47.7 56.15-47.7 56.15 47.7 56.16-47.7 56.15 47.7 56.15-47.7 56.16 47.7 56.15-47.7 56.15 47.7 55.39-47.7V-200q0 41.92-29.04 70.96Q761.92-100 720-100H240Zm480-60q17 0 28.5-11.5T760-200v-560H320v460h360v100q0 17 11.5 28.5T720-160ZM367.69-610v-60h226.92v60H367.69Zm0 120v-60h226.92v60H367.69Zm310-114.62q-14.69 0-25.04-10.34-10.34-10.35-10.34-25.04t10.34-25.04q10.35-10.34 25.04-10.34t25.04 10.34q10.35 10.35 10.35 25.04t-10.35 25.04q-10.35 10.34-25.04 10.34Zm0 120q-14.69 0-25.04-10.34-10.34-10.35-10.34-25.04t10.34-25.04q10.35-10.34 25.04-10.34t25.04 10.34q10.35 10.35 10.35 25.04t-10.35 25.04q-10.35 10.34-25.04 10.34ZM240-160h380v-80H200v40q0 17 11.5 28.5T240-160Zm-40 0v-80 80Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
rounded-md
|
||||
opacity-0
|
||||
bg-gray-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
text-black text-xs text-center
|
||||
mb-2
|
||||
p-2
|
||||
w-28
|
||||
group-hover:opacity-100
|
||||
transition-opacity
|
||||
duration-300
|
||||
"
|
||||
>
|
||||
Sales Invoice List
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="p-1 rounded-md bg-gray-100"
|
||||
:class="{
|
||||
hidden: !fyo.singles.AccountingSettings?.enableLoyaltyProgram,
|
||||
'bg-gray-100': loyaltyPoints,
|
||||
'dark:bg-gray-600 cursor-not-allowed':
|
||||
!loyaltyPoints || !sinvDoc.party || !sinvDoc.items?.length,
|
||||
}"
|
||||
@click="
|
||||
loyaltyPoints && sinvDoc.party && sinvDoc.items?.length
|
||||
? toggleModal('LoyaltyProgram', true)
|
||||
: null
|
||||
"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="23px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="25px"
|
||||
fill="#000"
|
||||
>
|
||||
<path
|
||||
d="M100-180v-600h760v600H100Zm50.26-50.26h659.48v-499.48H150.26v499.48Zm0 0v-499.48 499.48Zm181.64-56.77h50.25v-42.56h48.67q14.37 0 23.6-10.38 9.22-10.38 9.22-24.25v-106.93q0-14.71-9.22-24.88-9.23-10.17-23.6-10.17H298.77v-73.95h164.87v-50.26h-81.49v-42.56H331.9v42.56h-48.41q-14.63 0-24.8 10.38-10.18 10.38-10.18 25v106.27q0 14.62 10.18 23.71 10.17 9.1 24.8 9.1h129.9v76.1H248.51v50.26h83.39v42.56Zm312.97-27.94L705.9-376H583.85l61.02 61.03ZM583.85-574H705.9l-61.03-61.03L583.85-574Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
mb-2
|
||||
bg-gray-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
text-black text-xs
|
||||
rounded-md
|
||||
p-2
|
||||
w-28
|
||||
text-center
|
||||
opacity-0
|
||||
group-hover:opacity-100
|
||||
transition-opacity
|
||||
duration-300
|
||||
"
|
||||
>
|
||||
Loyalty Program
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="p-0.5 rounded-md bg-gray-100"
|
||||
:class="{
|
||||
hidden: !fyo.singles.AccountingSettings?.enableCouponCode,
|
||||
'bg-gray-100': loyaltyPoints,
|
||||
'dark:bg-gray-600 cursor-not-allowed':
|
||||
!sinvDoc.party || !sinvDoc.items?.length,
|
||||
}"
|
||||
@click="openCouponModal()"
|
||||
>
|
||||
<svg
|
||||
fill="#000000"
|
||||
width="28px"
|
||||
height="28px"
|
||||
viewBox="0 0 512.00 512.00"
|
||||
enable-background="new 0 0 512 512"
|
||||
version="1.1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
stroke="#000000"
|
||||
stroke-width="3.312000000000001"
|
||||
transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke="#CCCCCC"
|
||||
stroke-width="19.456"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<g id="Layer_1"></g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path
|
||||
d="M412.7,134.4H229.6c-2,0-3.9,0.8-5.3,2.2l-27.8,27.8L169.1,137c-1.4-1.4-3.3-2.2-5.3-2.2H99.3c-4.1,0-7.5,3.4-7.5,7.5 v227.4c0,4.1,3.4,7.5,7.5,7.5h64.5c2,0,3.9-0.8,5.3-2.2l27.4-27.4l27.8,27.8c1.4,1.4,3.3,2.2,5.3,2.2h183.1c4.1,0,7.5-3.4,7.5-7.5 V141.9C420.2,137.7,416.8,134.4,412.7,134.4z M405.2,362.6H232.7l-30.9-30.9c-2.9-2.9-7.7-2.9-10.6,0l-30.5,30.5h-53.9V149.8h53.9 l30.5,30.5c2.9,2.9,7.7,2.9,10.6,0l30.9-30.9h172.5V362.6z"
|
||||
></path>
|
||||
<path
|
||||
d="M276.9,235.2c15.4,0,28-12.6,28-28s-12.6-28-28-28s-28,12.6-28,28S261.4,235.2,276.9,235.2z M276.9,194.2 c7.2,0,13,5.8,13,13s-5.8,13-13,13s-13-5.8-13-13S269.7,194.2,276.9,194.2z"
|
||||
></path>
|
||||
<path
|
||||
d="M360,262.4c-15.4,0-28,12.6-28,28s12.6,28,28,28s28-12.6,28-28S375.4,262.4,360,262.4z M360,303.4c-7.2,0-13-5.8-13-13 s5.8-13,13-13s13,5.8,13,13S367.2,303.4,360,303.4z"
|
||||
></path>
|
||||
<path
|
||||
d="M256.6,310.7c1.5,1.5,3.4,2.2,5.3,2.2s3.8-0.7,5.3-2.2l113.1-113.1c2.9-2.9,2.9-7.7,0-10.6c-2.9-2.9-7.7-2.9-10.6,0 L256.6,300.1C253.6,303,253.6,307.7,256.6,310.7z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,202.5c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,203.3,198.4,202.5,196.5,202.5z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,233.2c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,234,198.4,233.2,196.5,233.2z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,263.8c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,264.6,198.4,263.8,196.5,263.8z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,294.5c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,295.3,198.4,294.5,196.5,294.5z"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
mb-2
|
||||
bg-gray-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
text-black text-xs
|
||||
rounded-md
|
||||
p-2
|
||||
w-28
|
||||
text-center
|
||||
opacity-0
|
||||
group-hover:opacity-100
|
||||
transition-opacity
|
||||
duration-300
|
||||
"
|
||||
>
|
||||
Coupon Code
|
||||
</span>
|
||||
<div
|
||||
v-if="appliedCouponsCount !== 0"
|
||||
class="
|
||||
absolute
|
||||
top-0
|
||||
right-0
|
||||
transform
|
||||
translate-x-1/2
|
||||
-translate-y-1/2
|
||||
h-4
|
||||
w-4
|
||||
bg-green-400
|
||||
text-green-900
|
||||
rounded-full
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
text-xs
|
||||
cursor-pointer
|
||||
border-red-500
|
||||
p-2
|
||||
"
|
||||
>
|
||||
{{ appliedCouponsCount }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-7">
|
||||
<div class="flex flex-col gap-3" style="height: calc(100vh - 6rem)">
|
||||
<div
|
||||
class="
|
||||
bg-white
|
||||
dark:bg-gray-850
|
||||
border
|
||||
dark:border-gray-800
|
||||
grow
|
||||
h-full
|
||||
p-4
|
||||
rounded-md
|
||||
"
|
||||
>
|
||||
<!-- Customer Search -->
|
||||
<MultiLabelLink
|
||||
v-if="sinvDoc.fieldMap"
|
||||
class="flex-shrink-0"
|
||||
secondary-link="phone"
|
||||
:border="true"
|
||||
:value="sinvDoc.party"
|
||||
:df="sinvDoc.fieldMap.party"
|
||||
@change="(value:string) => setCustomer(value)"
|
||||
/>
|
||||
|
||||
<SelectedItemTable />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="
|
||||
bg-white
|
||||
dark:bg-gray-850
|
||||
border
|
||||
dark:border-gray-800
|
||||
p-4
|
||||
rounded-md
|
||||
"
|
||||
>
|
||||
<div class="w-full grid grid-cols-2 gap-y-2 gap-x-3">
|
||||
<div class="">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<FloatingLabelFloatInput
|
||||
:df="{
|
||||
label: t`Total Quantity`,
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'totalQuantity',
|
||||
minvalue: 0,
|
||||
maxvalue: 1000,
|
||||
}"
|
||||
size="large"
|
||||
:value="totalQuantity"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
|
||||
<FloatingLabelCurrencyInput
|
||||
:df="{
|
||||
label: t`Add'l Discounts`,
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'additionalDiscount',
|
||||
minvalue: 0,
|
||||
}"
|
||||
size="large"
|
||||
:value="additionalDiscounts"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
@change="(amount:Money)=> additionalDiscounts= amount"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<FloatingLabelCurrencyInput
|
||||
:df="{
|
||||
label: t`Item Discounts`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'itemDiscounts',
|
||||
}"
|
||||
size="large"
|
||||
:value="itemDiscounts"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
<FloatingLabelCurrencyInput
|
||||
v-if="sinvDoc.fieldMap"
|
||||
:df="sinvDoc.fieldMap.grandTotal"
|
||||
size="large"
|
||||
:value="sinvDoc.grandTotal"
|
||||
:read-only="true"
|
||||
:text-right="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<Button
|
||||
class="w-full bg-violet-500 dark:bg-violet-700 py-6"
|
||||
:disabled="!sinvDoc.party || !sinvDoc.items?.length"
|
||||
@click="handleSaveInvoiceAction"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Save` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
<Button
|
||||
class="w-full mt-4 bg-blue-500 dark:bg-blue-700 py-6"
|
||||
@click="toggleModal('SavedInvoice', true)"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`held` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Button
|
||||
class="w-full bg-red-500 dark:bg-red-700 py-6"
|
||||
:disabled="!sinvDoc.items?.length"
|
||||
@click="clearValues"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Cancel` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="mt-4 w-full bg-green-500 dark:bg-green-700 py-6"
|
||||
:disabled="disablePayButton"
|
||||
@click="toggleModal('Payment', true)"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Pay` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Button from 'src/components/Button.vue';
|
||||
import ClosePOSShiftModal from './ClosePOSShiftModal.vue';
|
||||
import FloatingLabelCurrencyInput from 'src/components/POS/FloatingLabelCurrencyInput.vue';
|
||||
import FloatingLabelFloatInput from 'src/components/POS/FloatingLabelFloatInput.vue';
|
||||
import ItemsTable from 'src/components/POS/ItemsTable.vue';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import OpenPOSShiftModal from './OpenPOSShiftModal.vue';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import PaymentModal from './PaymentModal.vue';
|
||||
import SelectedItemTable from 'src/components/POS/SelectedItemTable.vue';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { routeTo, toggleSidebar } from 'src/utils/ui';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import ItemsGrid from 'src/components/POS/ItemsGrid.vue';
|
||||
import { t } from 'fyo';
|
||||
import {
|
||||
ItemQtyMap,
|
||||
ItemSerialNumbers,
|
||||
POSItem,
|
||||
} from 'src/components/POS/types';
|
||||
import { Money } from 'pesa';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import ModernPOS from './ModernPOS.vue';
|
||||
import ClassicPOS from './ClassicPOS.vue';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
import { ModalName } from 'src/components/POS/types';
|
||||
import { Money } from 'pesa';
|
||||
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { Shipment } from 'models/inventory/Shipment';
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
import { routeTo, toggleSidebar } from 'src/utils/ui';
|
||||
import PageHeader from 'src/components/PageHeader.vue';
|
||||
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
import {
|
||||
getItem,
|
||||
getItemDiscounts,
|
||||
validateSinv,
|
||||
getItemQtyMap,
|
||||
getItemDiscounts,
|
||||
validateShipment,
|
||||
getTotalQuantity,
|
||||
getTotalTaxedAmount,
|
||||
validateIsPosSettingsSet,
|
||||
validateShipment,
|
||||
validateSinv,
|
||||
} from 'src/utils/pos';
|
||||
import Barcode from 'src/components/Controls/Barcode.vue';
|
||||
import {
|
||||
getAddedLPWithGrandTotal,
|
||||
getPricingRule,
|
||||
removeFreeItems,
|
||||
getAddedLPWithGrandTotal,
|
||||
} from 'models/helpers';
|
||||
import LoyaltyProgramModal from './LoyaltyprogramModal.vue';
|
||||
import AlertModal from './AlertModal.vue';
|
||||
import SavedInvoiceModal from './SavedInvoiceModal.vue';
|
||||
import CouponCodeModal from './CouponCodeModal.vue';
|
||||
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
||||
import MultiLabelLink from 'src/components/Controls/MultiLabelLink.vue';
|
||||
import {
|
||||
POSItem,
|
||||
ItemQtyMap,
|
||||
ItemSerialNumbers,
|
||||
} from 'src/components/POS/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'POS',
|
||||
components: {
|
||||
Button,
|
||||
ClosePOSShiftModal,
|
||||
FloatingLabelCurrencyInput,
|
||||
FloatingLabelFloatInput,
|
||||
ItemsTable,
|
||||
ItemsGrid,
|
||||
Link,
|
||||
MultiLabelLink,
|
||||
AlertModal,
|
||||
OpenPOSShiftModal,
|
||||
ModernPOS,
|
||||
PageHeader,
|
||||
PaymentModal,
|
||||
LoyaltyProgramModal,
|
||||
SavedInvoiceModal,
|
||||
CouponCodeModal,
|
||||
SelectedItemTable,
|
||||
Barcode,
|
||||
ClassicPOS,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
cashAmount: computed(() => this.cashAmount),
|
||||
doc: computed(() => this.sinvDoc),
|
||||
isDiscountingEnabled: computed(() => this.isDiscountingEnabled),
|
||||
itemDiscounts: computed(() => this.itemDiscounts),
|
||||
itemQtyMap: computed(() => this.itemQtyMap),
|
||||
itemSerialNumbers: computed(() => this.itemSerialNumbers),
|
||||
sinvDoc: computed(() => this.sinvDoc),
|
||||
appliedCoupons: computed(() => this.sinvDoc.coupons),
|
||||
coupons: computed(() => this.coupons),
|
||||
totalTaxedAmount: computed(() => this.totalTaxedAmount),
|
||||
transferAmount: computed(() => this.transferAmount),
|
||||
transferClearanceDate: computed(() => this.transferClearanceDate),
|
||||
itemQtyMap: computed(() => this.itemQtyMap),
|
||||
cashAmount: computed(() => this.cashAmount),
|
||||
transferRefNo: computed(() => this.transferRefNo),
|
||||
itemDiscounts: computed(() => this.itemDiscounts),
|
||||
transferAmount: computed(() => this.transferAmount),
|
||||
appliedCoupons: computed(() => this.sinvDoc.coupons),
|
||||
totalTaxedAmount: computed(() => this.totalTaxedAmount),
|
||||
itemSerialNumbers: computed(() => this.itemSerialNumbers),
|
||||
isDiscountingEnabled: computed(() => this.isDiscountingEnabled),
|
||||
transferClearanceDate: computed(() => this.transferClearanceDate),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [] as POSItem[],
|
||||
|
||||
tableView: true,
|
||||
|
||||
isItemsSeeded: false,
|
||||
openPaymentModal: false,
|
||||
openLoyaltyProgramModal: false,
|
||||
openSavedInvoiceModal: false,
|
||||
openCouponCodeModal: false,
|
||||
openAppliedCouponsModal: false,
|
||||
openShiftCloseModal: false,
|
||||
openShiftOpenModal: false,
|
||||
openRouteToInvoiceListModal: false,
|
||||
items: [] as POSItem[],
|
||||
|
||||
additionalDiscounts: fyo.pesa(0),
|
||||
cashAmount: fyo.pesa(0),
|
||||
itemDiscounts: fyo.pesa(0),
|
||||
totalTaxedAmount: fyo.pesa(0),
|
||||
transferAmount: fyo.pesa(0),
|
||||
openAlertModal: false,
|
||||
openPaymentModal: false,
|
||||
openKeyboardModal: false,
|
||||
openCouponCodeModal: false,
|
||||
openShiftCloseModal: false,
|
||||
openSavedInvoiceModal: false,
|
||||
openLoyaltyProgramModal: false,
|
||||
openAppliedCouponsModal: false,
|
||||
|
||||
totalQuantity: 0,
|
||||
cashAmount: fyo.pesa(0),
|
||||
itemDiscounts: fyo.pesa(0),
|
||||
transferAmount: fyo.pesa(0),
|
||||
totalTaxedAmount: fyo.pesa(0),
|
||||
additionalDiscounts: fyo.pesa(0),
|
||||
|
||||
loyaltyPoints: 0,
|
||||
appliedLoyaltyPoints: 0,
|
||||
loyaltyProgram: '' as string,
|
||||
|
||||
appliedCoupons: [] as AppliedCouponCodes[],
|
||||
appliedCouponsCount: 0,
|
||||
appliedCoupons: [] as AppliedCouponCodes[],
|
||||
|
||||
defaultCustomer: undefined as string | undefined,
|
||||
itemSearchTerm: '',
|
||||
transferRefNo: undefined as string | undefined,
|
||||
|
||||
defaultCustomer: undefined as string | undefined,
|
||||
transferClearanceDate: undefined as Date | undefined,
|
||||
|
||||
itemQtyMap: {} as ItemQtyMap,
|
||||
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||
paymentDoc: {} as Payment,
|
||||
sinvDoc: {} as SalesInvoice,
|
||||
itemQtyMap: {} as ItemQtyMap,
|
||||
coupons: {} as AppliedCouponCodes,
|
||||
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -659,24 +192,11 @@ export default defineComponent({
|
||||
return !!fyo.singles.AccountingSettings?.enableDiscounting;
|
||||
},
|
||||
isPosShiftOpen: () => !!fyo.singles.POSShift?.isShiftOpen,
|
||||
isPaymentAmountSet(): boolean {
|
||||
if (this.sinvDoc.grandTotal?.isZero()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.cashAmount.isZero() && this.transferAmount.isZero()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
disablePayButton(): boolean {
|
||||
if (!this.sinvDoc.items?.length) {
|
||||
if (!this.sinvDoc.items?.length || !this.sinvDoc.party) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.sinvDoc.party) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
@ -816,7 +336,6 @@ export default defineComponent({
|
||||
},
|
||||
async setLoyaltyPoints(value: number) {
|
||||
this.appliedLoyaltyPoints = value;
|
||||
|
||||
this.sinvDoc.redeemLoyaltyPoints = true;
|
||||
|
||||
const totalLotaltyAmount = await getAddedLPWithGrandTotal(
|
||||
@ -851,7 +370,6 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
async addItem(item: POSItem | Item | undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
await this.sinvDoc.runFormulas();
|
||||
|
||||
if (!item) {
|
||||
@ -912,8 +430,10 @@ export default defineComponent({
|
||||
if (existingItems.length) {
|
||||
existingItems[0].rate = item.rate as Money;
|
||||
existingItems[0].quantity = (existingItems[0].quantity as number) + 1;
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -943,6 +463,7 @@ export default defineComponent({
|
||||
async makePayment() {
|
||||
this.paymentDoc = this.sinvDoc.getPayment() as Payment;
|
||||
const paymentMethod = this.cashAmount.isZero() ? 'Transfer' : 'Cash';
|
||||
|
||||
await this.paymentDoc.set('paymentMethod', paymentMethod);
|
||||
|
||||
if (paymentMethod === 'Transfer') {
|
||||
@ -1067,6 +588,7 @@ export default defineComponent({
|
||||
if (value) {
|
||||
return (this[`open${modal}Modal`] = value);
|
||||
}
|
||||
|
||||
return (this[`open${modal}Modal`] = !this[`open${modal}Modal`]);
|
||||
},
|
||||
updateValues() {
|
||||
@ -1087,6 +609,7 @@ export default defineComponent({
|
||||
this.sinvDoc.pricingRuleDetail = undefined;
|
||||
this.sinvDoc.isPricingRuleApplied = false;
|
||||
removeFreeItems(this.sinvDoc as SalesInvoice);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1113,16 +636,16 @@ export default defineComponent({
|
||||
return await routeTo('/list/SalesInvoice');
|
||||
}
|
||||
|
||||
this.openRouteToInvoiceListModal = true;
|
||||
this.openAlertModal = true;
|
||||
},
|
||||
async handleSaveInvoiceAction() {
|
||||
async saveInvoiceAction() {
|
||||
if (!this.sinvDoc.party && !this.sinvDoc.items?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.saveOrder();
|
||||
},
|
||||
routeTo,
|
||||
getItem,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
320
src/pages/POS/POSQuickActions.vue
Normal file
320
src/pages/POS/POSQuickActions.vue
Normal file
@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="relative group">
|
||||
<div class="bg-gray-100 p-1.5 rounded-md" @click="toggleItemsView">
|
||||
<FeatherIcon
|
||||
:name="tableView ? 'grid' : 'list'"
|
||||
class="w-5 h-5 text-black"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="
|
||||
p-2
|
||||
mb-2
|
||||
w-20
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
text-center
|
||||
opacity-0
|
||||
bg-gray-100
|
||||
text-black text-xs
|
||||
rounded-md
|
||||
transition-opacity
|
||||
duration-300
|
||||
group-hover:opacity-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
"
|
||||
>
|
||||
{{ tableView ? 'Grid View' : 'List View' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="px-1.5 py-1 rounded-md bg-gray-100"
|
||||
@click="() => $emit('emitRouteToSinvList')"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
width="21"
|
||||
fill="#000"
|
||||
>
|
||||
<path
|
||||
d="M240-100q-41.92 0-70.96-29.04Q140-158.08 140-199.82V-300h120v-552.31l55.39 47.7 56.15-47.7 56.15 47.7 56.16-47.7 56.15 47.7 56.15-47.7 56.16 47.7 56.15-47.7 56.15 47.7 55.39-47.7V-200q0 41.92-29.04 70.96Q761.92-100 720-100H240Zm480-60q17 0 28.5-11.5T760-200v-560H320v460h360v100q0 17 11.5 28.5T720-160ZM367.69-610v-60h226.92v60H367.69Zm0 120v-60h226.92v60H367.69Zm310-114.62q-14.69 0-25.04-10.34-10.34-10.35-10.34-25.04t10.34-25.04q10.35-10.34 25.04-10.34t25.04 10.34q10.35 10.35 10.35 25.04t-10.35 25.04q-10.35 10.34-25.04 10.34Zm0 120q-14.69 0-25.04-10.34-10.34-10.35-10.34-25.04t10.34-25.04q10.35-10.34 25.04-10.34t25.04 10.34q10.35 10.35 10.35 25.04t-10.35 25.04q-10.35 10.34-25.04 10.34ZM240-160h380v-80H200v40q0 17 11.5 28.5T240-160Zm-40 0v-80 80Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="
|
||||
mb-2
|
||||
p-2
|
||||
w-28
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
rounded-md
|
||||
opacity-0
|
||||
bg-gray-100
|
||||
text-black text-xs text-center
|
||||
transition-opacity
|
||||
duration-300
|
||||
group-hover:opacity-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
"
|
||||
>
|
||||
Sales Invoice List
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative group"
|
||||
:class="{
|
||||
hidden: !fyo.singles.AccountingSettings?.enableLoyaltyProgram,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="p-1 rounded-md bg-gray-100"
|
||||
:class="{
|
||||
'bg-gray-100': loyaltyPoints,
|
||||
'dark:bg-gray-600 cursor-not-allowed':
|
||||
!loyaltyPoints || !sinvDoc?.party || !sinvDoc?.items?.length,
|
||||
}"
|
||||
@click="
|
||||
loyaltyPoints && sinvDoc?.party && sinvDoc?.items?.length
|
||||
? toggleModal('LoyaltyProgram', true)
|
||||
: null
|
||||
"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="23px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="25px"
|
||||
fill="#000"
|
||||
>
|
||||
<path
|
||||
d="M100-180v-600h760v600H100Zm50.26-50.26h659.48v-499.48H150.26v499.48Zm0 0v-499.48 499.48Zm181.64-56.77h50.25v-42.56h48.67q14.37 0 23.6-10.38 9.22-10.38 9.22-24.25v-106.93q0-14.71-9.22-24.88-9.23-10.17-23.6-10.17H298.77v-73.95h164.87v-50.26h-81.49v-42.56H331.9v42.56h-48.41q-14.63 0-24.8 10.38-10.18 10.38-10.18 25v106.27q0 14.62 10.18 23.71 10.17 9.1 24.8 9.1h129.9v76.1H248.51v50.26h83.39v42.56Zm312.97-27.94L705.9-376H583.85l61.02 61.03ZM583.85-574H705.9l-61.03-61.03L583.85-574Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="
|
||||
mb-2
|
||||
p-2
|
||||
w-28
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
bg-gray-100
|
||||
text-black text-xs
|
||||
rounded-md
|
||||
text-center
|
||||
opacity-0
|
||||
transition-opacity
|
||||
duration-300
|
||||
group-hover:opacity-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
"
|
||||
>
|
||||
Loyalty Program
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative group"
|
||||
:class="{
|
||||
hidden: !fyo.singles.AccountingSettings?.enableCouponCode,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="p-0.5 rounded-md bg-gray-100"
|
||||
:class="{
|
||||
'bg-gray-100': loyaltyPoints,
|
||||
'dark:bg-gray-600 cursor-not-allowed':
|
||||
!sinvDoc?.party || !sinvDoc?.items?.length,
|
||||
}"
|
||||
@click="openCouponModal()"
|
||||
>
|
||||
<svg
|
||||
fill="#000000"
|
||||
width="28px"
|
||||
height="28px"
|
||||
viewBox="0 0 512.00 512.00"
|
||||
enable-background="new 0 0 512 512"
|
||||
version="1.1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
stroke="#000000"
|
||||
stroke-width="3.312000000000001"
|
||||
transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke="#CCCCCC"
|
||||
stroke-width="19.456"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<g id="Layer_1"></g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path
|
||||
d="M412.7,134.4H229.6c-2,0-3.9,0.8-5.3,2.2l-27.8,27.8L169.1,137c-1.4-1.4-3.3-2.2-5.3-2.2H99.3c-4.1,0-7.5,3.4-7.5,7.5 v227.4c0,4.1,3.4,7.5,7.5,7.5h64.5c2,0,3.9-0.8,5.3-2.2l27.4-27.4l27.8,27.8c1.4,1.4,3.3,2.2,5.3,2.2h183.1c4.1,0,7.5-3.4,7.5-7.5 V141.9C420.2,137.7,416.8,134.4,412.7,134.4z M405.2,362.6H232.7l-30.9-30.9c-2.9-2.9-7.7-2.9-10.6,0l-30.5,30.5h-53.9V149.8h53.9 l30.5,30.5c2.9,2.9,7.7,2.9,10.6,0l30.9-30.9h172.5V362.6z"
|
||||
></path>
|
||||
<path
|
||||
d="M276.9,235.2c15.4,0,28-12.6,28-28s-12.6-28-28-28s-28,12.6-28,28S261.4,235.2,276.9,235.2z M276.9,194.2 c7.2,0,13,5.8,13,13s-5.8,13-13,13s-13-5.8-13-13S269.7,194.2,276.9,194.2z"
|
||||
></path>
|
||||
<path
|
||||
d="M360,262.4c-15.4,0-28,12.6-28,28s12.6,28,28,28s28-12.6,28-28S375.4,262.4,360,262.4z M360,303.4c-7.2,0-13-5.8-13-13 s5.8-13,13-13s13,5.8,13,13S367.2,303.4,360,303.4z"
|
||||
></path>
|
||||
<path
|
||||
d="M256.6,310.7c1.5,1.5,3.4,2.2,5.3,2.2s3.8-0.7,5.3-2.2l113.1-113.1c2.9-2.9,2.9-7.7,0-10.6c-2.9-2.9-7.7-2.9-10.6,0 L256.6,300.1C253.6,303,253.6,307.7,256.6,310.7z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,202.5c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,203.3,198.4,202.5,196.5,202.5z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,233.2c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,234,198.4,233.2,196.5,233.2z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,263.8c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,264.6,198.4,263.8,196.5,263.8z"
|
||||
></path>
|
||||
<path
|
||||
d="M196.5,294.5c-2,0-3.9,0.8-5.3,2.2c-1.4,1.4-2.2,3.3-2.2,5.3c0,2,0.8,3.9,2.2,5.3c1.4,1.4,3.3,2.2,5.3,2.2 c2,0,3.9-0.8,5.3-2.2c1.4-1.4,2.2-3.3,2.2-5.3c0-2-0.8-3.9-2.2-5.3C200.4,295.3,198.4,294.5,196.5,294.5z"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="
|
||||
mb-2
|
||||
p-2
|
||||
w-28
|
||||
absolute
|
||||
bottom-full
|
||||
left-1/2
|
||||
transform
|
||||
-translate-x-1/2
|
||||
bg-gray-100
|
||||
text-black text-xs
|
||||
rounded-md
|
||||
text-center
|
||||
opacity-0
|
||||
transition-opacity
|
||||
duration-300
|
||||
group-hover:opacity-100
|
||||
dark:bg-gray-800 dark:text-white
|
||||
"
|
||||
>
|
||||
Coupon Code
|
||||
</span>
|
||||
<div
|
||||
v-if="appliedCouponsCount !== 0"
|
||||
class="
|
||||
h-4
|
||||
w-4
|
||||
p-2
|
||||
absolute
|
||||
top-0
|
||||
right-0
|
||||
transform
|
||||
translate-x-1/2
|
||||
-translate-y-1/2
|
||||
bg-green-400
|
||||
text-green-900
|
||||
border-red-500
|
||||
rounded-full
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
text-xs
|
||||
cursor-pointer
|
||||
"
|
||||
>
|
||||
{{ appliedCouponsCount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { ModalName } from 'src/components/POS/types';
|
||||
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||
import { ItemSerialNumbers } from 'src/components/POS/types';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
export default defineComponent({
|
||||
name: 'POSQuickActions',
|
||||
props: {
|
||||
openAlertModal: Boolean,
|
||||
loyaltyPoints: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
loyaltyProgram: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
appliedCouponsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
sinvDoc: {
|
||||
type: Object as PropType<SalesInvoice | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
emits: ['toggleView', 'toggleModal', 'emitRouteToSinvList'],
|
||||
data() {
|
||||
return {
|
||||
tableView: true,
|
||||
|
||||
totalQuantity: 0,
|
||||
totalTaxedAmount: fyo.pesa(0),
|
||||
additionalDiscounts: fyo.pesa(0),
|
||||
|
||||
paymentDoc: {} as Payment,
|
||||
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||
|
||||
itemSearchTerm: '',
|
||||
transferRefNo: undefined as string | undefined,
|
||||
transferClearanceDate: undefined as Date | undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPosShiftOpen: () => !!fyo.singles.POSShift?.isShiftOpen,
|
||||
},
|
||||
methods: {
|
||||
setTransferRefNo(ref: string) {
|
||||
this.transferRefNo = ref;
|
||||
},
|
||||
toggleItemsView() {
|
||||
this.tableView = !this.tableView;
|
||||
this.$emit('toggleView', !this.tableView);
|
||||
},
|
||||
toggleModal(modal: ModalName, value: boolean) {
|
||||
this.$emit('toggleModal', modal, value);
|
||||
},
|
||||
openCouponModal() {
|
||||
if (this.sinvDoc?.party && this.sinvDoc?.items?.length) {
|
||||
this.toggleModal('CouponCode', true);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -152,7 +152,7 @@
|
||||
<div class="row-start-6 grid grid-cols-2 gap-4 mt-auto">
|
||||
<div class="col-span-2">
|
||||
<Button
|
||||
class="w-full bg-red-500"
|
||||
class="w-full bg-red-500 dark:bg-red-700"
|
||||
style="padding: 1.35rem"
|
||||
@click="$emit('toggleModal', 'Payment')"
|
||||
>
|
||||
@ -166,7 +166,7 @@
|
||||
|
||||
<div class="col-span-1">
|
||||
<Button
|
||||
class="w-full bg-blue-500"
|
||||
class="w-full bg-blue-500 dark:bg-blue-700"
|
||||
style="padding: 1.35rem"
|
||||
:disabled="disableSubmitButton"
|
||||
@click="submitTransaction()"
|
||||
@ -180,7 +180,7 @@
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<Button
|
||||
class="w-full bg-green-500"
|
||||
class="w-full bg-green-500 dark:bg-green-700"
|
||||
style="padding: 1.35rem"
|
||||
:disabled="disableSubmitButton"
|
||||
@click="$emit('createTransaction', true)"
|
||||
|
@ -102,16 +102,12 @@ function getInventorySidebar(): SidebarRoot[] {
|
||||
}
|
||||
|
||||
function getPOSSidebar() {
|
||||
const isPOSEnabled = !!fyo.singles.InventorySettings?.enablePointOfSale;
|
||||
if (!isPOSEnabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
label: t`POS`,
|
||||
name: 'pos',
|
||||
route: '/pos',
|
||||
icon: 'pos',
|
||||
hidden: () => !fyo.singles.InventorySettings?.enablePointOfSale,
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user