mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +00:00
Merge pull request #1036 from AbleKSaju/feat-pricelist
feat: PriceList functionality in POS
This commit is contained in:
commit
08284b758f
@ -48,7 +48,7 @@ import { AppliedCouponCodes } from '../AppliedCouponCodes/AppliedCouponCodes';
|
||||
import { CouponCode } from '../CouponCode/CouponCode';
|
||||
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
||||
import { SalesInvoiceItem } from '../SalesInvoiceItem/SalesInvoiceItem';
|
||||
import { PriceListItem } from '../PriceList/PriceListItem';
|
||||
import { PricingRuleItem } from '../PricingRuleItem/PricingRuleItem';
|
||||
|
||||
export type TaxDetail = {
|
||||
account: string;
|
||||
@ -1332,7 +1332,7 @@ export abstract class Invoice extends Transactional {
|
||||
item: item.item as string,
|
||||
unit: item.unit as string,
|
||||
},
|
||||
})) as PriceListItem[];
|
||||
})) as PricingRuleItem[];
|
||||
|
||||
return docs.map((doc) => doc.parent) as string[];
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ import { safeParseFloat } from 'utils/index';
|
||||
import { Invoice } from '../Invoice/Invoice';
|
||||
import { Item } from '../Item/Item';
|
||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||
import { PriceList } from '../PriceList/PriceList';
|
||||
import { isPesa } from 'fyo/utils';
|
||||
import { PricingRule } from '../PricingRule/PricingRule';
|
||||
import { getItemRateFromPriceList } from 'models/helpers';
|
||||
|
||||
export abstract class InvoiceItem extends Doc {
|
||||
item?: string;
|
||||
@ -629,7 +629,10 @@ async function getItemRate(doc: InvoiceItem): Promise<Money | undefined> {
|
||||
let priceListRate: Money | undefined;
|
||||
|
||||
if (doc.fyo.singles.AccountingSettings?.enablePriceList) {
|
||||
priceListRate = await getItemRateFromPriceList(doc);
|
||||
priceListRate = await getItemRateFromPriceList(
|
||||
doc,
|
||||
doc.parentdoc?.priceList as string
|
||||
);
|
||||
}
|
||||
|
||||
if (priceListRate) {
|
||||
@ -675,43 +678,6 @@ async function getItemRateFromPricingRule(
|
||||
return pricingRuleDoc.discountRate;
|
||||
}
|
||||
|
||||
async function getItemRateFromPriceList(
|
||||
doc: InvoiceItem
|
||||
): Promise<Money | undefined> {
|
||||
const priceListName = doc.parentdoc?.priceList;
|
||||
const item = doc.item;
|
||||
if (!priceListName || !item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const priceList = await doc.fyo.doc.getDoc(
|
||||
ModelNameEnum.PriceList,
|
||||
priceListName
|
||||
);
|
||||
|
||||
if (!(priceList instanceof PriceList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = doc.unit;
|
||||
const transferUnit = doc.transferUnit;
|
||||
const plItem = priceList.priceListItem?.find((pli) => {
|
||||
if (pli.item !== item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (transferUnit && pli.unit !== transferUnit) {
|
||||
return false;
|
||||
} else if (unit && pli.unit !== unit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return plItem?.rate;
|
||||
}
|
||||
|
||||
function getDiscountedTotalBeforeTaxation(
|
||||
rate: Money,
|
||||
quantity: number,
|
||||
|
@ -33,6 +33,9 @@ import { ValidationError } from 'fyo/utils/errors';
|
||||
import { isPesa } from 'fyo/utils';
|
||||
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
||||
import { safeParseFloat } from 'utils/index';
|
||||
import { PriceList } from './baseModels/PriceList/PriceList';
|
||||
import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem';
|
||||
import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||
|
||||
export function getQuoteActions(
|
||||
fyo: Fyo,
|
||||
@ -947,6 +950,43 @@ export async function getPricingRule(
|
||||
return pricingRules;
|
||||
}
|
||||
|
||||
export async function getItemRateFromPriceList(
|
||||
doc: InvoiceItem | SalesInvoiceItem,
|
||||
priceListName: string
|
||||
): Promise<Money | undefined> {
|
||||
const item = doc.item;
|
||||
if (!priceListName || !item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const priceList = await doc.fyo.doc.getDoc(
|
||||
ModelNameEnum.PriceList,
|
||||
priceListName
|
||||
);
|
||||
|
||||
if (!(priceList instanceof PriceList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = doc.unit;
|
||||
const transferUnit = doc.transferUnit;
|
||||
const plItem = priceList.priceListItem?.find((pli) => {
|
||||
if (pli.item !== item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (transferUnit && pli.unit !== transferUnit) {
|
||||
return false;
|
||||
} else if (unit && pli.unit !== unit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return plItem?.rate;
|
||||
}
|
||||
|
||||
export function filterPricingRules(
|
||||
pricingRuleDocsForItem: PricingRule[],
|
||||
sinvDate: Date,
|
||||
|
@ -15,7 +15,8 @@ export type ModalName =
|
||||
| 'LoyaltyProgram'
|
||||
| 'SavedInvoice'
|
||||
| 'Alert'
|
||||
| 'CouponCode';
|
||||
| 'CouponCode'
|
||||
| 'PriceList';
|
||||
|
||||
export type PosEmits =
|
||||
| 'addItem'
|
||||
|
@ -34,6 +34,11 @@
|
||||
@set-coupons-count="(count) => emitEvent('setCouponsCount', count)"
|
||||
/>
|
||||
|
||||
<PriceListModal
|
||||
:open-modal="openPriceListModal"
|
||||
@toggle-modal="emitEvent('toggleModal', 'PriceList')"
|
||||
/>
|
||||
|
||||
<PaymentModal
|
||||
:open-modal="openPaymentModal"
|
||||
@toggle-modal="emitEvent('toggleModal', 'Payment')"
|
||||
@ -287,6 +292,7 @@ import AlertModal from './AlertModal.vue';
|
||||
import PaymentModal from './PaymentModal.vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import PriceListModal from './PriceListModal.vue';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import CouponCodeModal from './CouponCodeModal.vue';
|
||||
@ -318,8 +324,9 @@ export default defineComponent({
|
||||
ItemsTable,
|
||||
PaymentModal,
|
||||
MultiLabelLink,
|
||||
POSQuickActions,
|
||||
PriceListModal,
|
||||
CouponCodeModal,
|
||||
POSQuickActions,
|
||||
OpenPOSShiftModal,
|
||||
SelectedItemTable,
|
||||
SavedInvoiceModal,
|
||||
@ -336,6 +343,7 @@ export default defineComponent({
|
||||
isPosShiftOpen: Boolean,
|
||||
disablePayButton: Boolean,
|
||||
openPaymentModal: Boolean,
|
||||
openPriceListModal: Boolean,
|
||||
openCouponCodeModal: Boolean,
|
||||
openShiftCloseModal: Boolean,
|
||||
openSavedInvoiceModal: Boolean,
|
||||
|
@ -34,6 +34,11 @@
|
||||
@set-coupons-count="(count) => emitEvent('setCouponsCount', count)"
|
||||
/>
|
||||
|
||||
<PriceListModal
|
||||
:open-modal="openPriceListModal"
|
||||
@toggle-modal="emitEvent('toggleModal', 'PriceList')"
|
||||
/>
|
||||
|
||||
<PaymentModal
|
||||
:open-modal="openPaymentModal"
|
||||
@toggle-modal="emitEvent('toggleModal', 'Payment')"
|
||||
@ -297,6 +302,7 @@ import AlertModal from './AlertModal.vue';
|
||||
import PaymentModal from './PaymentModal.vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import KeyboardModal from './KeyboardModal.vue';
|
||||
import PriceListModal from './PriceListModal.vue';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import CouponCodeModal from './CouponCodeModal.vue';
|
||||
@ -327,6 +333,7 @@ export default defineComponent({
|
||||
PaymentModal,
|
||||
KeyboardModal,
|
||||
MultiLabelLink,
|
||||
PriceListModal,
|
||||
POSQuickActions,
|
||||
CouponCodeModal,
|
||||
OpenPOSShiftModal,
|
||||
@ -348,6 +355,7 @@ export default defineComponent({
|
||||
disablePayButton: Boolean,
|
||||
openPaymentModal: Boolean,
|
||||
openKeyboardModal: Boolean,
|
||||
openPriceListModal: Boolean,
|
||||
openCouponCodeModal: Boolean,
|
||||
openShiftCloseModal: Boolean,
|
||||
openSavedInvoiceModal: Boolean,
|
||||
|
@ -26,6 +26,7 @@
|
||||
:open-payment-modal="openPaymentModal"
|
||||
:item-discounts="(itemDiscounts as Money)"
|
||||
:coupons="(coupons as AppliedCouponCodes)"
|
||||
:open-price-list-modal="openPriceListModal"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
:open-shift-close-modal="openShiftCloseModal"
|
||||
:open-coupon-code-modal="openCouponCodeModal"
|
||||
@ -66,6 +67,7 @@
|
||||
:open-keyboard-modal="openKeyboardModal"
|
||||
:item-discounts="(itemDiscounts as Money)"
|
||||
:coupons="(coupons as AppliedCouponCodes)"
|
||||
:open-price-list-modal="openPriceListModal"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
:open-shift-close-modal="openShiftCloseModal"
|
||||
:open-coupon-code-modal="openCouponCodeModal"
|
||||
@ -124,6 +126,7 @@ import {
|
||||
getPricingRule,
|
||||
removeFreeItems,
|
||||
getAddedLPWithGrandTotal,
|
||||
getItemRateFromPriceList,
|
||||
} from 'models/helpers';
|
||||
import {
|
||||
POSItem,
|
||||
@ -165,6 +168,7 @@ export default defineComponent({
|
||||
openAlertModal: false,
|
||||
openPaymentModal: false,
|
||||
openKeyboardModal: false,
|
||||
openPriceListModal: false,
|
||||
openCouponCodeModal: false,
|
||||
openShiftCloseModal: false,
|
||||
openSavedInvoiceModal: false,
|
||||
@ -440,7 +444,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (existingItems.length) {
|
||||
existingItems[0].rate = item.rate as Money;
|
||||
if (!this.sinvDoc.priceList) {
|
||||
existingItems[0].rate = item.rate as Money;
|
||||
}
|
||||
|
||||
existingItems[0].quantity = (existingItems[0].quantity as number) + 1;
|
||||
|
||||
await this.applyPricingRule();
|
||||
@ -454,6 +461,17 @@ export default defineComponent({
|
||||
item: item.name,
|
||||
});
|
||||
|
||||
if (this.sinvDoc.priceList) {
|
||||
let itemData = this.sinvDoc.items?.filter(
|
||||
(val) => val.item == item.name
|
||||
) as SalesInvoiceItem[];
|
||||
|
||||
itemData[0].rate = await getItemRateFromPriceList(
|
||||
itemData[0],
|
||||
this.sinvDoc.priceList
|
||||
);
|
||||
}
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
},
|
||||
|
@ -139,7 +139,6 @@
|
||||
<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,
|
||||
}"
|
||||
@ -249,6 +248,54 @@
|
||||
{{ appliedCouponsCount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative group"
|
||||
:class="{
|
||||
hidden: !fyo.singles.AccountingSettings?.enablePriceList,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="p-1 rounded-md bg-gray-100"
|
||||
@click="$emit('toggleModal', 'PriceList')"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="23px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#000"
|
||||
>
|
||||
<path
|
||||
d="M180.31-164q-27.01 0-45.66-18.65Q116-201.3 116-228.31v-503.38q0-27.01 18.65-45.66Q153.3-796 180.31-796h599.38q27.01 0 45.66 18.65Q844-758.7 844-731.69v503.38q0 27.01-18.65 45.66Q806.7-164 779.69-164H180.31Zm0-52h599.38q4.62 0 8.46-3.85 3.85-3.84 3.85-8.46v-503.38q0-4.62-3.85-8.46-3.84-3.85-8.46-3.85H180.31q-4.62 0-8.46 3.85-3.85 3.84-3.85 8.46v503.38q0 4.62 3.85 8.46 3.84 3.85 8.46 3.85ZM221-297h172v-52H221v52Zm361-77.23L737.77-530 701-566.77l-119 119-51-51L494.23-462 582-374.23ZM221-454h172v-52H221v52Zm0-156h172v-52H221v52Zm-53 394v-528 528Z"
|
||||
/>
|
||||
</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
|
||||
"
|
||||
>
|
||||
Price List
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
109
src/pages/POS/PriceListModal.vue
Normal file
109
src/pages/POS/PriceListModal.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<Modal class="h-auto w-96" :set-close-listener="false">
|
||||
<p class="text-center font-semibold py-3">{{ t`Apply Price List` }}</p>
|
||||
<div class="px-10">
|
||||
<hr class="dark:border-gray-800" />
|
||||
<div class="flex justify-center pt-10">
|
||||
<div class="flex justify-between w-full mb-20">
|
||||
<div class="w-full">
|
||||
<Link
|
||||
v-if="sinvDoc.fieldMap"
|
||||
class="flex-shrink-0 w-full"
|
||||
:border="true"
|
||||
:value="priceList"
|
||||
:df="sinvDoc.fieldMap.priceList"
|
||||
@change="(value) => (priceList = value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-10 flex justify-end items-center">
|
||||
<feather-icon
|
||||
name="trash"
|
||||
class="w-5 text-xl text-red-500"
|
||||
@click="removePriceList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-start-6 grid grid-cols-2 gap-4 mt-auto mb-2">
|
||||
<div class="col-span-2">
|
||||
<Button
|
||||
class="w-full bg-green-500 dark:bg-green-700"
|
||||
style="padding: 1.35rem"
|
||||
@click="setPriceList"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Save` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-start-6 grid 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="$emit('toggleModal', 'PriceList')"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Cancel` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { t } from 'fyo';
|
||||
import Modal from 'src/components/Modal.vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
import Link from 'src/components/Controls/Link.vue';
|
||||
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PriceListModal',
|
||||
components: {
|
||||
Link,
|
||||
Modal,
|
||||
Button,
|
||||
},
|
||||
emits: ['toggleModal'],
|
||||
setup() {
|
||||
return {
|
||||
sinvDoc: inject('sinvDoc') as SalesInvoice,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
priceList: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async removePriceList() {
|
||||
this.priceList = '';
|
||||
await this.setPriceList();
|
||||
},
|
||||
async setPriceList() {
|
||||
try {
|
||||
await this.sinvDoc.set('priceList', this.priceList);
|
||||
|
||||
this.$emit('toggleModal', 'PriceList');
|
||||
} catch (error) {
|
||||
showToast({
|
||||
type: 'error',
|
||||
message: t`${error as string}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user