mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
feat : item enquiry
This commit is contained in:
@@ -23,6 +23,7 @@ export class AccountingSettings extends Doc {
|
||||
enableInvoiceReturns?: boolean;
|
||||
enableLoyaltyProgram?: boolean;
|
||||
enablePricingRule?: boolean;
|
||||
enaenableItemEnquiry?: boolean;
|
||||
enableERPNextSync?: boolean;
|
||||
enablePointOfSaleWithOutInventory?: boolean;
|
||||
enablePartialPayment?: boolean;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { ListViewSettings } from 'fyo/model/types';
|
||||
|
||||
export class ItemEnquiry extends Doc {
|
||||
itemName!: string;
|
||||
customerName!: string;
|
||||
contact?: string;
|
||||
description?: string;
|
||||
similarProduct?: string;
|
||||
|
||||
static override getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: [
|
||||
'itemName',
|
||||
'customerName',
|
||||
'contact',
|
||||
'description',
|
||||
'similarProduct',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import { ERPNextSyncSettings } from './baseModels/ERPNextSyncSettings/ERPNextSyn
|
||||
import { ERPNextSyncQueue } from './baseModels/ERPNextSyncQueue/ERPNextSyncQueue';
|
||||
import { FetchFromERPNextQueue } from './baseModels/FetchFromERPNextQueue/FetchFromERPNextQueue';
|
||||
import { IntegrationErrorLog } from './baseModels/IntegrationErrorLog/IntegrationErrorLog';
|
||||
import { ItemEnquiry } from './baseModels/ItemEnquiry/ItemEnquiry';
|
||||
|
||||
export const models = {
|
||||
Account,
|
||||
@@ -67,6 +68,7 @@ export const models = {
|
||||
Defaults,
|
||||
Item,
|
||||
ItemGroup,
|
||||
ItemEnquiry,
|
||||
JournalEntry,
|
||||
JournalEntryAccount,
|
||||
Misc,
|
||||
|
||||
@@ -21,6 +21,7 @@ export enum ModelNameEnum {
|
||||
Defaults = 'Defaults',
|
||||
Item = 'Item',
|
||||
ItemGroup = 'ItemGroup',
|
||||
ItemEnquiry = 'ItemEnquiry',
|
||||
UOM = 'UOM',
|
||||
UOMConversionItem = 'UOMConversionItem',
|
||||
JournalEntry = 'JournalEntry',
|
||||
|
||||
@@ -121,6 +121,13 @@
|
||||
"default": false,
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"fieldname": "enableItemEnquiry",
|
||||
"label": "Enable Item Enquiry",
|
||||
"fieldtype": "Check",
|
||||
"default": true,
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"fieldname": "enableLoyaltyProgram",
|
||||
"label": "Enable Loyalty Program",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ItemEnquiry",
|
||||
"label": "Item Enquiry",
|
||||
"create": false,
|
||||
"isSingle": false,
|
||||
"isChild": false,
|
||||
"naming": "autoincrement",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "itemName",
|
||||
"label": "Item Name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "customerName",
|
||||
"label": "Customer Name",
|
||||
"fieldtype": "Data",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "contact",
|
||||
"label": "Contact",
|
||||
"fieldtype": "Data",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"label": "Description",
|
||||
"fieldtype": "Text",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "similarProduct",
|
||||
"label": "Similar Product",
|
||||
"fieldtype": "Data",
|
||||
"readOnly": true,
|
||||
"section": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -81,6 +81,7 @@ import FetchFromERPNextQueue from './app/FetchFromERPNextQueue.json';
|
||||
import IntegrationErrorLog from './app/IntegrationErrorLog.json';
|
||||
import ItemGroup from './app/ItemGroup.json';
|
||||
import { Schema, SchemaStub } from './types';
|
||||
import ItemEnquiry from './app/ItemEnquiry.json';
|
||||
|
||||
export const coreSchemas: Schema[] = [
|
||||
PatchRun as Schema,
|
||||
@@ -132,6 +133,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
||||
JournalEntryAccount as Schema,
|
||||
|
||||
Invoice as Schema,
|
||||
ItemEnquiry as Schema,
|
||||
SalesInvoice as Schema,
|
||||
PurchaseInvoice as Schema,
|
||||
SalesQuote as Schema,
|
||||
|
||||
@@ -19,6 +19,7 @@ export const modalNames = [
|
||||
'Alert',
|
||||
'CouponCode',
|
||||
'PriceList',
|
||||
'ItemEnquiry',
|
||||
'ReturnSalesInvoice',
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
@toggle-modal="emitEvent('toggleModal', 'PriceList')"
|
||||
/>
|
||||
|
||||
<ItemEnquiryModal
|
||||
:open-modal="openItemEnquiryModal"
|
||||
@toggle-modal="emitEvent('toggleModal', 'ItemEnquiry')"
|
||||
/>
|
||||
|
||||
<PaymentModal
|
||||
:open-modal="openPaymentModal"
|
||||
@toggle-modal="emitEvent('toggleModal', 'Payment')"
|
||||
@@ -372,6 +377,7 @@ import PaymentModal from './PaymentModal.vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import PriceListModal from './PriceListModal.vue';
|
||||
import ItemEnquiryModal from './ItemEnquiryModal.vue';
|
||||
import { Item } from 'models/baseModels/Item/Item';
|
||||
import CouponCodeModal from './CouponCodeModal.vue';
|
||||
import POSQuickActions from './POSQuickActions.vue';
|
||||
@@ -404,6 +410,7 @@ export default defineComponent({
|
||||
PaymentModal,
|
||||
MultiLabelLink,
|
||||
PriceListModal,
|
||||
ItemEnquiryModal,
|
||||
CouponCodeModal,
|
||||
POSQuickActions,
|
||||
OpenPOSShiftModal,
|
||||
@@ -424,6 +431,7 @@ export default defineComponent({
|
||||
disablePayButton: Boolean,
|
||||
openPaymentModal: Boolean,
|
||||
openPriceListModal: Boolean,
|
||||
openItemEnquiryModal: Boolean,
|
||||
openCouponCodeModal: Boolean,
|
||||
openShiftCloseModal: Boolean,
|
||||
openSavedInvoiceModal: Boolean,
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<Modal class="h-auto w-96" :set-close-listener="false">
|
||||
<p class="text-center font-semibold py-3">{{ t`Item Enquiry` }}</p>
|
||||
<div class="px-10">
|
||||
<hr class="dark:border-gray-800" />
|
||||
<div class="flex flex-col gap-5 pt-8">
|
||||
<div class="relative">
|
||||
<label class="text-sm font-medium">{{ t`Item Name` }} *</label>
|
||||
<input
|
||||
v-model="form.itemName"
|
||||
type="text"
|
||||
required
|
||||
class="
|
||||
w-full
|
||||
border
|
||||
dark:border-gray-700
|
||||
rounded
|
||||
px-3
|
||||
py-2
|
||||
mt-1
|
||||
dark:bg-gray-800 dark:text-white
|
||||
focus:outline-none
|
||||
"
|
||||
autocomplete="off"
|
||||
@input="suggestSimilarProduct"
|
||||
@focus="dropdownOpen = true"
|
||||
@blur="dropdownOpen = false"
|
||||
/>
|
||||
<ul
|
||||
v-if="dropdownOpen"
|
||||
class="
|
||||
absolute
|
||||
z-50
|
||||
bg-white
|
||||
dark:bg-gray-800
|
||||
shadow-md
|
||||
border border-gray-300
|
||||
dark:border-gray-700
|
||||
w-40
|
||||
max-h-40
|
||||
overflow-y-auto
|
||||
rounded
|
||||
mt-1
|
||||
"
|
||||
>
|
||||
<li
|
||||
v-for="item in itemOptions"
|
||||
:key="item.name"
|
||||
class="
|
||||
px-3
|
||||
py-1
|
||||
text-sm text-gray-800
|
||||
dark:text-white
|
||||
cursor-pointer
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-gray-700
|
||||
"
|
||||
@mousedown.prevent="
|
||||
form.itemName = item.name;
|
||||
form.description = item.description || '';
|
||||
dropdownOpen = false;
|
||||
suggestSimilarProduct();
|
||||
"
|
||||
>
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<label class="text-sm font-medium">{{ t`Customer Name` }}</label>
|
||||
<input
|
||||
v-model="form.customerName"
|
||||
type="text"
|
||||
class="
|
||||
w-full
|
||||
border
|
||||
dark:border-gray-700
|
||||
rounded
|
||||
px-3
|
||||
py-2
|
||||
mt-1
|
||||
dark:bg-gray-800 dark:text-white
|
||||
focus:outline-none
|
||||
"
|
||||
autocomplete="off"
|
||||
@focus="partyDropdownOpen = true"
|
||||
/>
|
||||
<ul
|
||||
v-if="partyDropdownOpen"
|
||||
class="
|
||||
absolute
|
||||
z-50
|
||||
bg-white
|
||||
dark:bg-gray-800
|
||||
shadow-md
|
||||
border border-gray-300
|
||||
dark:border-gray-700
|
||||
w-full
|
||||
max-h-40
|
||||
overflow-y-auto
|
||||
rounded
|
||||
mt-1
|
||||
"
|
||||
>
|
||||
<li
|
||||
v-for="party in parties"
|
||||
:key="party.name"
|
||||
class="
|
||||
px-3
|
||||
py-1
|
||||
text-sm text-gray-800
|
||||
dark:text-white
|
||||
cursor-pointer
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-gray-700
|
||||
"
|
||||
@mousedown.prevent="
|
||||
form.customerName = party.name;
|
||||
form.contact = party.phone;
|
||||
partyDropdownOpen = false;
|
||||
"
|
||||
>
|
||||
{{ party.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium">{{ t`Contact` }}</label>
|
||||
<input
|
||||
v-model="form.contact"
|
||||
type="text"
|
||||
class="
|
||||
w-full
|
||||
border
|
||||
dark:border-gray-700
|
||||
rounded
|
||||
px-3
|
||||
py-2
|
||||
mt-1
|
||||
dark:bg-gray-800 dark:text-white
|
||||
focus:outline-none focus:ring-0
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium">{{ t`Description` }}</label>
|
||||
<textarea
|
||||
v-model="form.description"
|
||||
rows="2"
|
||||
class="
|
||||
w-full
|
||||
border
|
||||
dark:border-gray-700
|
||||
rounded
|
||||
px-3
|
||||
py-2
|
||||
mt-1
|
||||
dark:bg-gray-800 dark:text-white
|
||||
focus:outline-none focus:ring-0
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium">{{ t`Similar Product` }}</label>
|
||||
<input
|
||||
v-model="form.similarProduct"
|
||||
type="text"
|
||||
class="
|
||||
w-full
|
||||
border
|
||||
dark:border-gray-700
|
||||
rounded
|
||||
px-3
|
||||
py-2
|
||||
mt-1
|
||||
dark:bg-gray-800 dark:text-white
|
||||
focus:outline-none focus:ring-0
|
||||
"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mt-10 mb-4">
|
||||
<div class="col-span-2">
|
||||
<Button
|
||||
class="w-full bg-green-500 dark:bg-green-700"
|
||||
style="padding: 1.35rem"
|
||||
@click="submitForm"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Submit` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="col-span-2">
|
||||
<Button
|
||||
class="w-full bg-red-500 dark:bg-red-700"
|
||||
style="padding: 1.35rem"
|
||||
@click="closeModal"
|
||||
>
|
||||
<slot>
|
||||
<p class="uppercase text-lg text-white font-semibold">
|
||||
{{ t`Cancel` }}
|
||||
</p>
|
||||
</slot>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { t } from 'fyo';
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
import Modal from 'src/components/Modal.vue';
|
||||
import Button from 'src/components/Button.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ItemEnquiryModal',
|
||||
components: {
|
||||
Modal,
|
||||
Button,
|
||||
},
|
||||
emits: ['toggleModal'],
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
itemName: '',
|
||||
customerName: '',
|
||||
contact: '',
|
||||
description: '',
|
||||
similarProduct: '',
|
||||
},
|
||||
itemOptions: [] as { name: string; description?: string }[],
|
||||
dropdownOpen: false,
|
||||
|
||||
parties: [] as { name: string; phone: string }[],
|
||||
partyDropdownOpen: false,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchItems();
|
||||
await this.fetchParties();
|
||||
},
|
||||
methods: {
|
||||
async fetchItems() {
|
||||
try {
|
||||
const items = await this.fyo.db.getAll('Item', {
|
||||
filters: {},
|
||||
fields: ['name', 'description'],
|
||||
});
|
||||
|
||||
this.itemOptions = (items as Record<string, string>[])
|
||||
.filter((item) => item.name)
|
||||
.map((item) => ({
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
}));
|
||||
} catch (error) {
|
||||
showToast({ type: 'error', message: t`Failed to load items.` });
|
||||
}
|
||||
},
|
||||
|
||||
async fetchParties() {
|
||||
try {
|
||||
const partyData = await this.fyo.db.getAll('Party', {
|
||||
filters: { role: ['in', ['Customer', 'Both']] },
|
||||
fields: ['name', 'phone'],
|
||||
});
|
||||
|
||||
this.parties = (partyData as Record<string, string>[])
|
||||
.filter((p) => p.name && p.phone)
|
||||
.map((p) => ({
|
||||
name: p.name,
|
||||
phone: p.phone,
|
||||
}));
|
||||
} catch (error) {
|
||||
showToast({ type: 'error', message: t`Failed to load customers.` });
|
||||
}
|
||||
},
|
||||
|
||||
suggestSimilarProduct() {
|
||||
const input = this.form.itemName.toLowerCase().trim();
|
||||
if (!input) {
|
||||
this.form.similarProduct = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const match = this.itemOptions.find(
|
||||
(item) =>
|
||||
input.includes(item.name.toLowerCase()) &&
|
||||
item.name.toLowerCase() !== input
|
||||
);
|
||||
|
||||
this.form.similarProduct = match ? match.name : '';
|
||||
},
|
||||
|
||||
clearFormValues() {
|
||||
this.form = {
|
||||
itemName: '',
|
||||
customerName: '',
|
||||
contact: '',
|
||||
description: '',
|
||||
similarProduct: '',
|
||||
};
|
||||
this.dropdownOpen = false;
|
||||
this.partyDropdownOpen = false;
|
||||
},
|
||||
|
||||
async submitForm() {
|
||||
if (!this.form.itemName.trim()) {
|
||||
showToast({
|
||||
type: 'error',
|
||||
message: t`Item Name is required`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const doc = this.fyo.doc.getNewDoc('ItemEnquiry');
|
||||
doc.itemName = this.form.itemName;
|
||||
doc.customerName = this.form.customerName;
|
||||
doc.contact = this.form.contact;
|
||||
doc.description = this.form.description;
|
||||
doc.similarProduct = this.form.similarProduct;
|
||||
|
||||
await doc.sync();
|
||||
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: t`Item enquiry submitted`,
|
||||
});
|
||||
|
||||
this.clearFormValues();
|
||||
this.$emit('toggleModal', 'ItemEnquiry');
|
||||
} catch (error) {
|
||||
showToast({
|
||||
type: 'error',
|
||||
message: t`${error as string}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.clearFormValues();
|
||||
this.$emit('toggleModal', 'ItemEnquiry');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -32,6 +32,7 @@
|
||||
:item-discounts="(itemDiscounts as Money)"
|
||||
:coupons="(coupons as AppliedCouponCodes)"
|
||||
:open-price-list-modal="openPriceListModal"
|
||||
:open-item-enquiry-modal="openItemEnquiryModal"
|
||||
:applied-coupons-count="appliedCouponsCount"
|
||||
:open-shift-close-modal="openShiftCloseModal"
|
||||
:open-coupon-code-modal="openCouponCodeModal"
|
||||
@@ -203,6 +204,7 @@ export default defineComponent({
|
||||
openPaymentModal: false,
|
||||
openKeyboardModal: false,
|
||||
openPriceListModal: false,
|
||||
openItemEnquiryModal: false,
|
||||
openCouponCodeModal: false,
|
||||
openShiftCloseModal: false,
|
||||
openSavedInvoiceModal: false,
|
||||
|
||||
@@ -296,6 +296,53 @@
|
||||
Price List
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="relative group"
|
||||
:class="{
|
||||
hidden: !fyo.singles.AccountingSettings?.enableItemEnquiry,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="p-1 rounded-md bg-gray-100"
|
||||
@click="$emit('toggleModal', 'ItemEnquiry')"
|
||||
>
|
||||
<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
|
||||
"
|
||||
>
|
||||
Item Enquiry
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
Reference in New Issue
Block a user