mirror of
https://github.com/frappe/books.git
synced 2025-02-02 20:18:26 +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 {
|
||||||
import { Doc } from 'fyo/model/doc';
|
AccountRootType,
|
||||||
|
AccountRootTypeEnum,
|
||||||
|
} from './baseModels/Account/types';
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
ColumnConfig,
|
ColumnConfig,
|
||||||
@ -7,31 +9,30 @@ import {
|
|||||||
LeadStatus,
|
LeadStatus,
|
||||||
RenderData,
|
RenderData,
|
||||||
} from 'fyo/model/types';
|
} 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 { DateTime } from 'luxon';
|
||||||
import { Money } from 'pesa';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { safeParseFloat } from 'utils/index';
|
|
||||||
import { Router } from 'vue-router';
|
|
||||||
import {
|
|
||||||
AccountRootType,
|
|
||||||
AccountRootTypeEnum,
|
|
||||||
} from './baseModels/Account/types';
|
|
||||||
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
|
||||||
import { Invoice } from './baseModels/Invoice/Invoice';
|
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 { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
|
||||||
import { StockMovement } from './inventory/StockMovement';
|
import { StockMovement } from './inventory/StockMovement';
|
||||||
import { StockTransfer } from './inventory/StockTransfer';
|
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 { ValidationError } from 'fyo/utils/errors';
|
||||||
|
import { isPesa } from 'fyo/utils';
|
||||||
|
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
||||||
|
import { safeParseFloat } from 'utils/index';
|
||||||
|
|
||||||
export function getQuoteActions(
|
export function getQuoteActions(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
@ -763,6 +764,19 @@ export function getLoyaltyProgramTier(
|
|||||||
return loyaltyProgramTier;
|
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) {
|
export async function removeLoyaltyPoint(doc: Doc) {
|
||||||
if (!doc.loyaltyProgram) {
|
if (!doc.loyaltyProgram) {
|
||||||
return;
|
return;
|
||||||
|
@ -9,6 +9,7 @@ export class POSSettings extends Doc {
|
|||||||
inventory?: string;
|
inventory?: string;
|
||||||
cashAccount?: string;
|
cashAccount?: string;
|
||||||
writeOffAccount?: string;
|
writeOffAccount?: string;
|
||||||
|
posUI?: 'Classic' | 'Modern';
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
cashAccount: () => ({
|
cashAccount: () => ({
|
||||||
|
@ -31,6 +31,24 @@
|
|||||||
"create": true,
|
"create": true,
|
||||||
"default": "Write Off",
|
"default": "Write Off",
|
||||||
"section": "Default"
|
"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>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="
|
class="
|
||||||
|
px-2
|
||||||
|
w-36
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
border
|
border
|
||||||
w-36
|
|
||||||
rounded
|
rounded
|
||||||
px-2
|
|
||||||
bg-gray-50
|
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
|
focus-within:bg-gray-100
|
||||||
dark:focus-within:bg-gray-900
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
:tabindex="isReadOnly ? '-1' : '0'"
|
:tabindex="isReadOnly ? '-1' : '0'"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@input="(e) => $emit('input', e)"
|
@input="(e:Event) => $emit('input', e)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-show="!showInput"
|
v-show="!showInput"
|
||||||
@ -47,6 +47,17 @@ export default defineComponent({
|
|||||||
currencySymbol: '',
|
currencySymbol: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
focusInput: Boolean,
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.focusInput) {
|
||||||
|
this.showInput = true;
|
||||||
|
nextTick(() => {
|
||||||
|
this.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedValue() {
|
formattedValue() {
|
||||||
const value = this.parse(this.value);
|
const value = this.parse(this.value);
|
||||||
|
@ -1,51 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="
|
class="
|
||||||
flex flex-col
|
|
||||||
gap-4
|
gap-4
|
||||||
p-4
|
py-2
|
||||||
|
w-full
|
||||||
|
flex flex-col
|
||||||
items-center
|
items-center
|
||||||
mt-4
|
|
||||||
px-2
|
|
||||||
rounded-t-md
|
rounded-t-md
|
||||||
text-black
|
text-black
|
||||||
w-full
|
overflow-y-auto
|
||||||
|
custom-scroll custom-scroll-thumb2
|
||||||
"
|
"
|
||||||
style="height: 80vh"
|
style="height: 83vh"
|
||||||
>
|
>
|
||||||
<!-- Items Grid -->
|
<!-- 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
|
<div
|
||||||
class="
|
class="
|
||||||
border border-gray-300
|
|
||||||
dark:border-gray-800
|
|
||||||
p-1
|
p-1
|
||||||
|
border border-gray-300
|
||||||
flex flex-col
|
flex flex-col
|
||||||
text-sm text-center
|
text-sm text-center
|
||||||
|
dark:border-gray-800
|
||||||
"
|
"
|
||||||
@click="handleChange(item as POSItem)"
|
@click="handleChange(item as POSItem)"
|
||||||
v-for="item in items as POSItem[]"
|
v-for="item in items as POSItem[]"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
>
|
>
|
||||||
<div class="self-center w-32 h-32 rounded-lg mb-1">
|
<div class="self-center w-32 h-32 p-1 rounded-lg">
|
||||||
<div class="relative">
|
<div class="relative w-full h-full p-2">
|
||||||
<img
|
<img
|
||||||
v-if="item.image"
|
v-if="item.image"
|
||||||
:src="item.image"
|
:src="item.image"
|
||||||
alt=""
|
alt=""
|
||||||
class="rounded-lg w-32 h-32 object-cover"
|
class="rounded-lg w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="
|
class="
|
||||||
rounded-lg
|
rounded-lg
|
||||||
w-32
|
w-full
|
||||||
h-32
|
h-full
|
||||||
flex
|
|
||||||
bg-gray-100
|
bg-gray-100
|
||||||
dark:bg-gray-850
|
flex
|
||||||
justify-center
|
justify-center
|
||||||
items-center
|
items-center
|
||||||
|
dark:bg-gray-850
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<p class="text-4xl font-semibold text-gray-400 select-none">
|
<p class="text-4xl font-semibold text-gray-400 select-none">
|
||||||
@ -53,7 +63,17 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
<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="
|
:class="
|
||||||
item.availableQty > 0
|
item.availableQty > 0
|
||||||
? 'bg-green-100 text-green-900'
|
? 'bg-green-100 text-green-900'
|
||||||
@ -79,8 +99,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { POSItem } from '../types';
|
||||||
import { POSItem } from './types';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ItemsGrid',
|
name: 'ItemsGrid',
|
@ -29,7 +29,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<div class="overflow-y-auto" style="height: 80vh">
|
<div
|
||||||
|
class="overflow-y-auto custom-scroll custom-scroll-thumb2"
|
||||||
|
style="height: 70vh"
|
||||||
|
>
|
||||||
<Row
|
<Row
|
||||||
v-if="items"
|
v-if="items"
|
||||||
v-for="row in items as any"
|
v-for="row in items as any"
|
||||||
@ -64,12 +67,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 Row from 'src/components/Row.vue';
|
||||||
import { isNumeric } from 'src/utils';
|
import { isNumeric } from 'src/utils';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { Field } from 'schemas/types';
|
import { Field } from 'schemas/types';
|
||||||
import { POSItem } from './types';
|
import { POSItem } from '../types';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ItemsTable',
|
name: 'ItemsTable',
|
@ -262,18 +262,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Currency from '../Controls/Currency.vue';
|
import Currency from 'src/components/Controls/Currency.vue';
|
||||||
import Data from '../Controls/Data.vue';
|
import Data from 'src/components/Controls/Data.vue';
|
||||||
import Float from '../Controls/Float.vue';
|
import Float from 'src/components/Controls/Float.vue';
|
||||||
import Int from '../Controls/Int.vue';
|
import Int from 'src/components/Controls/Int.vue';
|
||||||
import Link from '../Controls/Link.vue';
|
import Link from 'src/components/Controls/Link.vue';
|
||||||
import Text from '../Controls/Text.vue';
|
import Text from 'src/components/Controls/Text.vue';
|
||||||
import { inject } from 'vue';
|
import { inject } from 'vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { DiscountType } from './types';
|
import { DiscountType } from '../types';
|
||||||
import { validateSerialNumberCount } from 'src/utils/pos';
|
import { validateSerialNumberCount } from 'src/utils/pos';
|
||||||
import { getPricingRule } from 'models/helpers';
|
import { getPricingRule } from 'models/helpers';
|
||||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
@ -11,7 +11,7 @@
|
|||||||
w-full
|
w-full
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
mt-4
|
mt-2
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -60,10 +60,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FormContainer from '../FormContainer.vue';
|
import FormContainer from 'src/components/FormContainer.vue';
|
||||||
import FormControl from '../Controls/FormControl.vue';
|
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||||
import Link from '../Controls/Link.vue';
|
import Link from 'src/components/Controls/Link.vue';
|
||||||
import Row from '../Row.vue';
|
import Row from 'src/components/Row.vue';
|
||||||
import RowEditForm from 'src/pages/CommonForm/RowEditForm.vue';
|
import RowEditForm from 'src/pages/CommonForm/RowEditForm.vue';
|
||||||
import SelectedItemRow from './SelectedItemRow.vue';
|
import SelectedItemRow from './SelectedItemRow.vue';
|
||||||
import { isNumeric } from 'src/utils';
|
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 DiscountType = 'percent' | 'amount';
|
||||||
|
|
||||||
export type ModalName =
|
export type ModalName =
|
||||||
| 'ShiftOpen'
|
| 'Keyboard'
|
||||||
| 'ShiftClose'
|
|
||||||
| 'Payment'
|
| 'Payment'
|
||||||
|
| 'ShiftClose'
|
||||||
| 'LoyaltyProgram'
|
| 'LoyaltyProgram'
|
||||||
| 'SavedInvoice'
|
| 'SavedInvoice'
|
||||||
| 'RouteToInvoiceList'
|
| 'Alert'
|
||||||
| 'CouponCode';
|
| 'CouponCode';
|
||||||
|
|
||||||
export interface POSItem {
|
export interface POSItem {
|
||||||
|
id?: number;
|
||||||
image?: string;
|
image?: string;
|
||||||
name: string;
|
name: string;
|
||||||
rate: Money;
|
rate: Money;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<div class="flex col-span-2 gap-5">
|
<div class="flex col-span-2 gap-5">
|
||||||
<Button
|
<Button
|
||||||
class="py-5 w-full bg-red-500 dark:bg-red-700"
|
class="py-5 w-full bg-red-500 dark:bg-red-700"
|
||||||
@click="$emit('toggleModal', 'RouteToInvoiceList')"
|
@click="$emit('toggleModal', 'Alert')"
|
||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<p class="uppercase text-lg text-white font-semibold">
|
<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"
|
class="w-full py-5 bg-green-500 dark:bg-green-700"
|
||||||
@click="
|
@click="
|
||||||
routeTo('/list/SalesInvoice');
|
routeTo('/list/SalesInvoice');
|
||||||
$emit('toggleModal', 'RouteToInvoiceList');
|
$emit('toggleModal', 'Alert');
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
@ -49,7 +49,7 @@ export default defineComponent({
|
|||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
},
|
},
|
||||||
emits: ['toggleModal', 'selectedInvoiceName'],
|
emits: ['toggleModal'],
|
||||||
methods: {
|
methods: {
|
||||||
routeTo,
|
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: {
|
methods: {
|
||||||
async updateLoyaltyPoints(newValue: number) {
|
async updateLoyaltyPoints(newValue: number) {
|
||||||
try {
|
try {
|
||||||
|
const partyData = await this.fyo.db.get(
|
||||||
|
ModelNameEnum.Party,
|
||||||
|
this.sinvDoc.party as string
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!partyData.loyaltyProgram) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.loyaltyPoints >= newValue) {
|
if (this.loyaltyPoints >= newValue) {
|
||||||
this.sinvDoc.loyaltyPoints = newValue;
|
this.sinvDoc.loyaltyPoints = newValue;
|
||||||
} else {
|
} else {
|
||||||
@ -121,7 +130,7 @@ export default defineComponent({
|
|||||||
ModelNameEnum.LoyaltyProgram,
|
ModelNameEnum.LoyaltyProgram,
|
||||||
{
|
{
|
||||||
fields: ['conversionFactor'],
|
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>
|
<template>
|
||||||
<div class="">
|
<div class="flex-col">
|
||||||
<PageHeader :title="t`Point of Sale`">
|
<PageHeader :title="t`Point of Sale`">
|
||||||
<slot>
|
<slot>
|
||||||
<Button
|
<Button
|
||||||
@ -10,646 +10,179 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</slot>
|
</slot>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
<ClassicPOS
|
||||||
<OpenPOSShiftModal
|
v-if="fyo.singles.POSSettings?.posUI == 'Classic'"
|
||||||
v-if="!isPosShiftOpen"
|
:item-quantity-qap="itemQtyMap"
|
||||||
:open-modal="!isPosShiftOpen"
|
|
||||||
@toggle-modal="toggleModal"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ClosePOSShiftModal
|
|
||||||
:open-modal="openShiftCloseModal"
|
|
||||||
@toggle-modal="toggleModal"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoyaltyProgramModal
|
|
||||||
:open-modal="openLoyaltyProgramModal"
|
|
||||||
:loyalty-points="loyaltyPoints"
|
:loyalty-points="loyaltyPoints"
|
||||||
:loyalty-program="loyaltyProgram"
|
:open-alert-modal="openAlertModal"
|
||||||
@set-loyalty-points="setLoyaltyPoints"
|
:default-customer="defaultCustomer"
|
||||||
@toggle-modal="toggleModal"
|
:items="(items as [] as POSItem[])"
|
||||||
/>
|
:cash-amount="(cashAmount as Money)"
|
||||||
<SavedInvoiceModal
|
:sinv-doc="(sinvDoc as SalesInvoice)"
|
||||||
:open-modal="openSavedInvoiceModal"
|
:disable-pay-button="disablePayButton"
|
||||||
:modal-status="openSavedInvoiceModal"
|
:open-payment-modal="openPaymentModal"
|
||||||
@selected-invoice-name="selectedInvoiceName"
|
:item-discounts="(itemDiscounts as Money)"
|
||||||
@toggle-modal="toggleModal"
|
:coupons="(coupons as AppliedCouponCodes)"
|
||||||
/>
|
:applied-coupons-count="appliedCouponsCount"
|
||||||
|
:open-shift-close-modal="openShiftCloseModal"
|
||||||
<CouponCodeModal
|
:open-coupon-code-modal="openCouponCodeModal"
|
||||||
:open-modal="openCouponCodeModal"
|
:open-saved-invoice-modal="openSavedInvoiceModal"
|
||||||
@set-coupons-count="setCouponsCount"
|
:open-loyalty-program-modal="openLoyaltyProgramModal"
|
||||||
@toggle-modal="toggleModal"
|
:open-applied-coupons-modal="openAppliedCouponsModal"
|
||||||
/>
|
@add-item="addItem"
|
||||||
|
@set-sinv-doc="setSinvDoc"
|
||||||
<PaymentModal
|
@clear-values="clearValues"
|
||||||
:open-modal="openPaymentModal"
|
@set-customer="setCustomer"
|
||||||
@create-transaction="createTransaction"
|
|
||||||
@toggle-modal="toggleModal"
|
@toggle-modal="toggleModal"
|
||||||
@set-cash-amount="setCashAmount"
|
@set-cash-amount="setCashAmount"
|
||||||
@set-transfer-amount="setTransferAmount"
|
|
||||||
@set-transfer-ref-no="setTransferRefNo"
|
|
||||||
@set-coupons-count="setCouponsCount"
|
@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"
|
||||||
/>
|
/>
|
||||||
|
<ModernPOS
|
||||||
<AlertModal
|
v-else
|
||||||
:open-modal="openRouteToInvoiceListModal"
|
: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"
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { t } from 'fyo';
|
||||||
import {
|
import { Money } from 'pesa';
|
||||||
ItemQtyMap,
|
import { fyo } from 'src/initFyo';
|
||||||
ItemSerialNumbers,
|
import ModernPOS from './ModernPOS.vue';
|
||||||
POSItem,
|
import ClassicPOS from './ClassicPOS.vue';
|
||||||
} from 'src/components/POS/types';
|
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 { Item } from 'models/baseModels/Item/Item';
|
||||||
import { ModalName } from 'src/components/POS/types';
|
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 { 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 {
|
import {
|
||||||
getItem,
|
validateSinv,
|
||||||
getItemDiscounts,
|
|
||||||
getItemQtyMap,
|
getItemQtyMap,
|
||||||
|
getItemDiscounts,
|
||||||
|
validateShipment,
|
||||||
getTotalQuantity,
|
getTotalQuantity,
|
||||||
getTotalTaxedAmount,
|
getTotalTaxedAmount,
|
||||||
validateIsPosSettingsSet,
|
validateIsPosSettingsSet,
|
||||||
validateShipment,
|
|
||||||
validateSinv,
|
|
||||||
} from 'src/utils/pos';
|
} from 'src/utils/pos';
|
||||||
import Barcode from 'src/components/Controls/Barcode.vue';
|
|
||||||
import {
|
import {
|
||||||
getAddedLPWithGrandTotal,
|
|
||||||
getPricingRule,
|
getPricingRule,
|
||||||
removeFreeItems,
|
removeFreeItems,
|
||||||
|
getAddedLPWithGrandTotal,
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import LoyaltyProgramModal from './LoyaltyprogramModal.vue';
|
import {
|
||||||
import AlertModal from './AlertModal.vue';
|
POSItem,
|
||||||
import SavedInvoiceModal from './SavedInvoiceModal.vue';
|
ItemQtyMap,
|
||||||
import CouponCodeModal from './CouponCodeModal.vue';
|
ItemSerialNumbers,
|
||||||
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
|
} from 'src/components/POS/types';
|
||||||
import MultiLabelLink from 'src/components/Controls/MultiLabelLink.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'POS',
|
name: 'POS',
|
||||||
components: {
|
components: {
|
||||||
Button,
|
Button,
|
||||||
ClosePOSShiftModal,
|
ModernPOS,
|
||||||
FloatingLabelCurrencyInput,
|
|
||||||
FloatingLabelFloatInput,
|
|
||||||
ItemsTable,
|
|
||||||
ItemsGrid,
|
|
||||||
Link,
|
|
||||||
MultiLabelLink,
|
|
||||||
AlertModal,
|
|
||||||
OpenPOSShiftModal,
|
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PaymentModal,
|
ClassicPOS,
|
||||||
LoyaltyProgramModal,
|
|
||||||
SavedInvoiceModal,
|
|
||||||
CouponCodeModal,
|
|
||||||
SelectedItemTable,
|
|
||||||
Barcode,
|
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
cashAmount: computed(() => this.cashAmount),
|
|
||||||
doc: computed(() => this.sinvDoc),
|
doc: computed(() => this.sinvDoc),
|
||||||
isDiscountingEnabled: computed(() => this.isDiscountingEnabled),
|
|
||||||
itemDiscounts: computed(() => this.itemDiscounts),
|
|
||||||
itemQtyMap: computed(() => this.itemQtyMap),
|
|
||||||
itemSerialNumbers: computed(() => this.itemSerialNumbers),
|
|
||||||
sinvDoc: computed(() => this.sinvDoc),
|
sinvDoc: computed(() => this.sinvDoc),
|
||||||
appliedCoupons: computed(() => this.sinvDoc.coupons),
|
|
||||||
coupons: computed(() => this.coupons),
|
coupons: computed(() => this.coupons),
|
||||||
totalTaxedAmount: computed(() => this.totalTaxedAmount),
|
itemQtyMap: computed(() => this.itemQtyMap),
|
||||||
transferAmount: computed(() => this.transferAmount),
|
cashAmount: computed(() => this.cashAmount),
|
||||||
transferClearanceDate: computed(() => this.transferClearanceDate),
|
|
||||||
transferRefNo: computed(() => this.transferRefNo),
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
items: [] as POSItem[],
|
|
||||||
|
|
||||||
tableView: true,
|
tableView: true,
|
||||||
|
|
||||||
isItemsSeeded: false,
|
items: [] as POSItem[],
|
||||||
openPaymentModal: false,
|
|
||||||
openLoyaltyProgramModal: false,
|
|
||||||
openSavedInvoiceModal: false,
|
|
||||||
openCouponCodeModal: false,
|
|
||||||
openAppliedCouponsModal: false,
|
|
||||||
openShiftCloseModal: false,
|
|
||||||
openShiftOpenModal: false,
|
|
||||||
openRouteToInvoiceListModal: false,
|
|
||||||
|
|
||||||
additionalDiscounts: fyo.pesa(0),
|
openAlertModal: false,
|
||||||
cashAmount: fyo.pesa(0),
|
openPaymentModal: false,
|
||||||
itemDiscounts: fyo.pesa(0),
|
openKeyboardModal: false,
|
||||||
totalTaxedAmount: fyo.pesa(0),
|
openCouponCodeModal: false,
|
||||||
transferAmount: fyo.pesa(0),
|
openShiftCloseModal: false,
|
||||||
|
openSavedInvoiceModal: false,
|
||||||
|
openLoyaltyProgramModal: false,
|
||||||
|
openAppliedCouponsModal: false,
|
||||||
|
|
||||||
totalQuantity: 0,
|
totalQuantity: 0,
|
||||||
|
cashAmount: fyo.pesa(0),
|
||||||
|
itemDiscounts: fyo.pesa(0),
|
||||||
|
transferAmount: fyo.pesa(0),
|
||||||
|
totalTaxedAmount: fyo.pesa(0),
|
||||||
|
additionalDiscounts: fyo.pesa(0),
|
||||||
|
|
||||||
loyaltyPoints: 0,
|
loyaltyPoints: 0,
|
||||||
appliedLoyaltyPoints: 0,
|
appliedLoyaltyPoints: 0,
|
||||||
loyaltyProgram: '' as string,
|
loyaltyProgram: '' as string,
|
||||||
|
|
||||||
appliedCoupons: [] as AppliedCouponCodes[],
|
|
||||||
appliedCouponsCount: 0,
|
appliedCouponsCount: 0,
|
||||||
|
appliedCoupons: [] as AppliedCouponCodes[],
|
||||||
|
|
||||||
defaultCustomer: undefined as string | undefined,
|
|
||||||
itemSearchTerm: '',
|
itemSearchTerm: '',
|
||||||
transferRefNo: undefined as string | undefined,
|
transferRefNo: undefined as string | undefined,
|
||||||
|
defaultCustomer: undefined as string | undefined,
|
||||||
transferClearanceDate: undefined as Date | undefined,
|
transferClearanceDate: undefined as Date | undefined,
|
||||||
|
|
||||||
itemQtyMap: {} as ItemQtyMap,
|
|
||||||
itemSerialNumbers: {} as ItemSerialNumbers,
|
|
||||||
paymentDoc: {} as Payment,
|
paymentDoc: {} as Payment,
|
||||||
sinvDoc: {} as SalesInvoice,
|
sinvDoc: {} as SalesInvoice,
|
||||||
|
itemQtyMap: {} as ItemQtyMap,
|
||||||
coupons: {} as AppliedCouponCodes,
|
coupons: {} as AppliedCouponCodes,
|
||||||
|
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -659,24 +192,11 @@ export default defineComponent({
|
|||||||
return !!fyo.singles.AccountingSettings?.enableDiscounting;
|
return !!fyo.singles.AccountingSettings?.enableDiscounting;
|
||||||
},
|
},
|
||||||
isPosShiftOpen: () => !!fyo.singles.POSShift?.isShiftOpen,
|
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 {
|
disablePayButton(): boolean {
|
||||||
if (!this.sinvDoc.items?.length) {
|
if (!this.sinvDoc.items?.length || !this.sinvDoc.party) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.sinvDoc.party) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -816,7 +336,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
async setLoyaltyPoints(value: number) {
|
async setLoyaltyPoints(value: number) {
|
||||||
this.appliedLoyaltyPoints = value;
|
this.appliedLoyaltyPoints = value;
|
||||||
|
|
||||||
this.sinvDoc.redeemLoyaltyPoints = true;
|
this.sinvDoc.redeemLoyaltyPoints = true;
|
||||||
|
|
||||||
const totalLotaltyAmount = await getAddedLPWithGrandTotal(
|
const totalLotaltyAmount = await getAddedLPWithGrandTotal(
|
||||||
@ -851,7 +370,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async addItem(item: POSItem | Item | undefined) {
|
async addItem(item: POSItem | Item | undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
await this.sinvDoc.runFormulas();
|
await this.sinvDoc.runFormulas();
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
@ -912,8 +430,10 @@ export default defineComponent({
|
|||||||
if (existingItems.length) {
|
if (existingItems.length) {
|
||||||
existingItems[0].rate = item.rate as Money;
|
existingItems[0].rate = item.rate as Money;
|
||||||
existingItems[0].quantity = (existingItems[0].quantity as number) + 1;
|
existingItems[0].quantity = (existingItems[0].quantity as number) + 1;
|
||||||
|
|
||||||
await this.applyPricingRule();
|
await this.applyPricingRule();
|
||||||
await this.sinvDoc.runFormulas();
|
await this.sinvDoc.runFormulas();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -943,6 +463,7 @@ export default defineComponent({
|
|||||||
async makePayment() {
|
async makePayment() {
|
||||||
this.paymentDoc = this.sinvDoc.getPayment() as Payment;
|
this.paymentDoc = this.sinvDoc.getPayment() as Payment;
|
||||||
const paymentMethod = this.cashAmount.isZero() ? 'Transfer' : 'Cash';
|
const paymentMethod = this.cashAmount.isZero() ? 'Transfer' : 'Cash';
|
||||||
|
|
||||||
await this.paymentDoc.set('paymentMethod', paymentMethod);
|
await this.paymentDoc.set('paymentMethod', paymentMethod);
|
||||||
|
|
||||||
if (paymentMethod === 'Transfer') {
|
if (paymentMethod === 'Transfer') {
|
||||||
@ -1067,6 +588,7 @@ export default defineComponent({
|
|||||||
if (value) {
|
if (value) {
|
||||||
return (this[`open${modal}Modal`] = value);
|
return (this[`open${modal}Modal`] = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this[`open${modal}Modal`] = !this[`open${modal}Modal`]);
|
return (this[`open${modal}Modal`] = !this[`open${modal}Modal`]);
|
||||||
},
|
},
|
||||||
updateValues() {
|
updateValues() {
|
||||||
@ -1087,6 +609,7 @@ export default defineComponent({
|
|||||||
this.sinvDoc.pricingRuleDetail = undefined;
|
this.sinvDoc.pricingRuleDetail = undefined;
|
||||||
this.sinvDoc.isPricingRuleApplied = false;
|
this.sinvDoc.isPricingRuleApplied = false;
|
||||||
removeFreeItems(this.sinvDoc as SalesInvoice);
|
removeFreeItems(this.sinvDoc as SalesInvoice);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1113,16 +636,16 @@ export default defineComponent({
|
|||||||
return await routeTo('/list/SalesInvoice');
|
return await routeTo('/list/SalesInvoice');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openRouteToInvoiceListModal = true;
|
this.openAlertModal = true;
|
||||||
},
|
},
|
||||||
async handleSaveInvoiceAction() {
|
async saveInvoiceAction() {
|
||||||
if (!this.sinvDoc.party && !this.sinvDoc.items?.length) {
|
if (!this.sinvDoc.party && !this.sinvDoc.items?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveOrder();
|
await this.saveOrder();
|
||||||
},
|
},
|
||||||
routeTo,
|
routeTo,
|
||||||
getItem,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</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="row-start-6 grid grid-cols-2 gap-4 mt-auto">
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<Button
|
<Button
|
||||||
class="w-full bg-red-500"
|
class="w-full bg-red-500 dark:bg-red-700"
|
||||||
style="padding: 1.35rem"
|
style="padding: 1.35rem"
|
||||||
@click="$emit('toggleModal', 'Payment')"
|
@click="$emit('toggleModal', 'Payment')"
|
||||||
>
|
>
|
||||||
@ -166,7 +166,7 @@
|
|||||||
|
|
||||||
<div class="col-span-1">
|
<div class="col-span-1">
|
||||||
<Button
|
<Button
|
||||||
class="w-full bg-blue-500"
|
class="w-full bg-blue-500 dark:bg-blue-700"
|
||||||
style="padding: 1.35rem"
|
style="padding: 1.35rem"
|
||||||
:disabled="disableSubmitButton"
|
:disabled="disableSubmitButton"
|
||||||
@click="submitTransaction()"
|
@click="submitTransaction()"
|
||||||
@ -180,7 +180,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-span-1">
|
<div class="col-span-1">
|
||||||
<Button
|
<Button
|
||||||
class="w-full bg-green-500"
|
class="w-full bg-green-500 dark:bg-green-700"
|
||||||
style="padding: 1.35rem"
|
style="padding: 1.35rem"
|
||||||
:disabled="disableSubmitButton"
|
:disabled="disableSubmitButton"
|
||||||
@click="$emit('createTransaction', true)"
|
@click="$emit('createTransaction', true)"
|
||||||
|
@ -102,16 +102,12 @@ function getInventorySidebar(): SidebarRoot[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPOSSidebar() {
|
function getPOSSidebar() {
|
||||||
const isPOSEnabled = !!fyo.singles.InventorySettings?.enablePointOfSale;
|
|
||||||
if (!isPOSEnabled) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: t`POS`,
|
label: t`POS`,
|
||||||
name: 'pos',
|
name: 'pos',
|
||||||
route: '/pos',
|
route: '/pos',
|
||||||
icon: 'pos',
|
icon: 'pos',
|
||||||
|
hidden: () => !fyo.singles.InventorySettings?.enablePointOfSale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user