2
0
mirror of https://github.com/frappe/books.git synced 2025-03-29 22:42:21 +00:00

feat: split selected items table and rows for ClassicPOS and ModernPOS

This commit is contained in:
AbleKSaju 2024-10-30 15:57:51 +05:30
parent a4de661a42
commit b05c28c00a
4 changed files with 539 additions and 12 deletions

View File

@ -262,18 +262,18 @@
</template>
<script lang="ts">
import Currency from '../Controls/Currency.vue';
import Data from '../Controls/Data.vue';
import Float from '../Controls/Float.vue';
import Int from '../Controls/Int.vue';
import Link from '../Controls/Link.vue';
import Text from '../Controls/Text.vue';
import Currency from 'src/components/Controls/Currency.vue';
import Data from 'src/components/Controls/Data.vue';
import Float from 'src/components/Controls/Float.vue';
import Int from 'src/components/Controls/Int.vue';
import Link from 'src/components/Controls/Link.vue';
import Text from 'src/components/Controls/Text.vue';
import { inject } from 'vue';
import { fyo } from 'src/initFyo';
import { defineComponent } from 'vue';
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
import { Money } from 'pesa';
import { DiscountType } from './types';
import { DiscountType } from '../types';
import { validateSerialNumberCount } from 'src/utils/pos';
import { getPricingRule } from 'models/helpers';
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';

View File

@ -11,7 +11,7 @@
w-full
flex
items-center
mt-4
mt-2
"
>
<div
@ -60,10 +60,10 @@
</template>
<script lang="ts">
import FormContainer from '../FormContainer.vue';
import FormControl from '../Controls/FormControl.vue';
import Link from '../Controls/Link.vue';
import Row from '../Row.vue';
import FormContainer from 'src/components/FormContainer.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import Link from 'src/components/Controls/Link.vue';
import Row from 'src/components/Row.vue';
import RowEditForm from 'src/pages/CommonForm/RowEditForm.vue';
import SelectedItemRow from './SelectedItemRow.vue';
import { isNumeric } from 'src/utils';

View File

@ -0,0 +1,378 @@
<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: calc(27vw)">
<div class="px-4 col-span-2">
<Float
:df="{
fieldname: 'quantity',
fieldtype: 'Float',
label: 'Quantity',
}"
@click="handleOpenKeyboard(row)"
size="medium"
:min="0"
:border="true"
:show-label="true"
:value="row.quantity"
@change="(value:number) => setQuantity((row.quantity = value))"
: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"
@change="(value:string) => row.set('transferUnit', value)"
: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',
}"
size="medium"
:border="true"
:show-label="true"
:value="row.transferQuantity"
@change="(value:string) => row.set('transferQuantity', value)"
:read-only="isReadOnly"
/>
</div>
<div class="px-4 pt-6 col-span-2">
<Currency
:df="{
fieldtype: 'Currency',
fieldname: 'rate',
label: 'Rate',
}"
size="medium"
:show-label="true"
:border="true"
:value="row.rate"
:read-only="isReadOnly"
@change="(value:Money) => setRate((row.rate = value))"
/>
</div>
<div class="px-4 col-span-2 mt-5">
<Currency
v-if="isDiscountingEnabled"
:df="{
fieldtype: 'Currency',
fieldname: 'discountAmount',
label: 'Discount Amount',
}"
class="col-span-2"
size="medium"
:show-label="true"
:border="true"
:value="row.itemDiscountAmount"
:read-only="row.itemDiscountPercent as number > 0 || isReadOnly"
@change="(value:number) => setItemDiscount('amount', value)"
/>
</div>
<div class="px-4 col-span-2 mt-5">
<Float
v-if="isDiscountingEnabled"
:df="{
fieldtype: 'Float',
fieldname: 'itemDiscountPercent',
label: 'Discount Percent',
}"
size="medium"
:show-label="true"
:border="true"
:value="row.itemDiscountPercent"
:read-only="!row.itemDiscountAmount?.isZero() || isReadOnly"
@change="(value:number) => setItemDiscount('percent', value)"
/>
</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-2 pt-8 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 { DiscountType } from '../types';
import { validateSerialNumberCount } from 'src/utils/pos';
import { getPricingRule } from 'models/helpers';
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
import { ApplicablePricingRules } from 'models/baseModels/Invoice/types';
export default defineComponent({
name: 'ModernPOSSelectedItemRow',
components: { Currency, Data, Float, Int, Link, Text },
props: {
row: { type: SalesInvoiceItem, required: true },
},
emits: ['toggleModal', 'runSinvFormulas', 'selectedItem'],
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) {
if (this.isReadOnly) {
return;
}
this.$emit('selectedItem', row);
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!
);
},
setItemDiscount(type: DiscountType, value: Money | number) {
if (type === 'percent') {
this.row.set('setItemDiscountAmount', false);
this.row.set('itemDiscountPercent', value as number);
return;
}
this.row.set('setItemDiscountAmount', true);
this.row.set('itemDiscountAmount', value as Money);
},
setRate(rate: Money) {
this.row.setRate = rate;
this.$emit('runSinvFormulas');
},
async setQuantity(quantity: number) {
this.row.set('quantity', quantity);
if (!this.row.isFreeItem) {
await this.updatePricingRuleItem();
this.$emit('runSinvFormulas');
}
},
async removeAddedItem(row: SalesInvoiceItem) {
this.row.parentdoc?.remove('items', row?.idx as number);
if (!row.isFreeItem) {
await this.updatePricingRuleItem();
}
},
async updatePricingRuleItem() {
const pricingRule = (await getPricingRule(
this.row.parentdoc as SalesInvoice
)) as ApplicablePricingRules[];
let appliedPricingRuleCount =
this.row.parentdoc?.pricingRuleDetail?.length;
if (appliedPricingRuleCount !== pricingRule?.length) {
appliedPricingRuleCount = pricingRule?.length;
await this.row.parentdoc?.appendPricingRuleDetail(pricingRule);
await this.row.parentdoc?.applyProductDiscount();
}
},
},
});
</script>

View File

@ -0,0 +1,149 @@
<template>
<Row
:ratio="ratio"
class="
w-full
px-2
mt-2
border
rounded-t
dark:border-gray-800
text-gray-600
dark:text-gray-400
"
>
<div
v-if="tableFields"
v-for="df in tableFields"
:key="df.fieldname"
class="text-lg flex px-2 py-2"
:class="{
'ms-auto': isNumeric(df as Field),
}"
>
{{ df.label }}
</div>
</Row>
<div
class="overflow-y-auto overflow-x-auto custom-scroll custom-scroll-thumb1"
style="height: calc(90vh - 25rem)"
>
<Row
v-for="row in sinvDoc.items"
:ratio="ratio"
class="
border
dark:border-gray-800
w-full
px-2
py-2
hover:bg-gray-25
dark:bg-gray-890
"
>
<ModernPOSSelectedItemRow
:row="(row as SalesInvoiceItem)"
@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 } from 'vue';
import { 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,
};
},
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('toggle-modal', modal);
},
async runSinvFormulas() {
await this.sinvDoc.runFormulas();
},
isNumeric,
},
});
</script>