Merge pull request #1245 from frappe/feat-item-group

feat: item group functionality
This commit is contained in:
Able k Saju
2025-06-12 11:18:49 +05:30
committed by GitHub
12 changed files with 134 additions and 2 deletions
@@ -71,6 +71,9 @@ export class AccountingSettings extends Doc {
enablePointOfSaleWithOutInventory: () => {
return !!this.enablePointOfSaleWithOutInventory;
},
enableitemGroup: () => {
return !!this.enableitemGroup;
},
};
override hidden: HiddenMap = {
+19 -1
View File
@@ -299,11 +299,29 @@ export abstract class InvoiceItem extends Doc {
},
tax: {
formula: async () => {
return (await this.fyo.getValue(
const itemTax = (await this.fyo.getValue(
'Item',
this.item as string,
'tax'
)) as string;
if (itemTax) {
return itemTax;
}
const itemGroup = (await this.fyo.getValue(
'Item',
this.item as string,
'itemGroup'
)) as string;
if (!itemGroup) {
return '';
}
const itemGroupDoc = await this.fyo.doc.getDoc('ItemGroup', itemGroup);
return itemGroupDoc?.tax as string;
},
dependsOn: ['item'],
},
+16
View File
@@ -26,6 +26,7 @@ export class Item extends Doc {
itemType?: 'Product' | 'Service';
for?: 'Purchases' | 'Sales' | 'Both';
hasBatch?: boolean;
itemGroup?: string;
hasSerialNumber?: boolean;
uomConversions: UOMConversionItem[] = [];
@@ -63,6 +64,20 @@ export class Item extends Doc {
},
dependsOn: ['itemType', 'trackItem'],
},
hsnCode: {
formula: async () => {
if (!this.itemGroup) {
return '';
}
const itemGroupDoc = await this.fyo.doc.getDoc(
'ItemGroup',
this.itemGroup
);
return itemGroupDoc?.hsnCode as string;
},
dependsOn: ['itemGroup'],
},
};
async beforeSync(): Promise<void> {
@@ -156,6 +171,7 @@ export class Item extends Doc {
),
uomConversions: () =>
!this.fyo.singles.InventorySettings?.enableUomConversions,
itemGroup: () => !this.fyo.singles.AccountingSettings?.enableitemGroup,
};
readOnly: ReadOnlyMap = {
+10
View File
@@ -0,0 +1,10 @@
import { Doc } from 'fyo/model/doc';
import { ListViewSettings } from 'fyo/model/types';
export class ItemGroup extends Doc {
static getListViewSettings(): ListViewSettings {
return {
columns: ['name', 'tax', 'hsnCode'],
};
}
}
+2
View File
@@ -31,6 +31,7 @@ import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem
import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
import { SalesQuoteItem } from './baseModels/SalesQuoteItem/SalesQuoteItem';
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
import { ItemGroup } from './baseModels/ItemGroup/ItemGroup';
import { Tax } from './baseModels/Tax/Tax';
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
import { Batch } from './inventory/Batch';
@@ -64,6 +65,7 @@ export const models = {
Batch,
Defaults,
Item,
ItemGroup,
JournalEntry,
JournalEntryAccount,
Misc,
+7
View File
@@ -135,6 +135,13 @@
"default": false,
"section": "Features"
},
{
"fieldname": "enableitemGroup",
"label": "Enable Item Group",
"fieldtype": "Check",
"default": false,
"section": "Features"
},
{
"fieldname": "enablePointOfSaleWithOutInventory",
"label": "Enable POS Without Inventory",
+8
View File
@@ -25,6 +25,14 @@
"placeholder": "Item Code",
"section": "Default"
},
{
"fieldname": "itemGroup",
"label": "Item Group",
"fieldtype": "Link",
"target": "ItemGroup",
"create": true,
"placeholder": "Item Group"
},
{
"fieldname": "for",
"label": "Purpose",
+31
View File
@@ -0,0 +1,31 @@
{
"name": "ItemGroup",
"label": "item Group",
"naming": "manual",
"fields": [
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"placeholder": "Name",
"section": "Default",
"required": true
},
{
"fieldname": "tax",
"label": "Tax",
"fieldtype": "Link",
"target": "Tax",
"create": true,
"placeholder": "Tax"
},
{
"fieldname": "hsnCode",
"label": "HSN/SAC",
"fieldtype": "Data",
"placeholder": "HSN/SAC Code"
}
],
"quickEditFields": ["tax", "hsnCode"],
"keywordFields": ["name"]
}
+2
View File
@@ -78,6 +78,7 @@ import ERPNextSyncSettings from './app/ERPNextSyncSettings.json';
import ERPNextSyncQueue from './app/ERPNextSyncQueue.json';
import FetchFromERPNextQueue from './app/FetchFromERPNextQueue.json';
import IntegrationErrorLog from './app/IntegrationErrorLog.json';
import ItemGroup from './app/ItemGroup.json';
import { Schema, SchemaStub } from './types';
export const coreSchemas: Schema[] = [
@@ -113,6 +114,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
Party as Schema,
Lead as Schema,
Address as Schema,
ItemGroup as Schema,
Item as Schema,
UOM as Schema,
UOMConversionItem as Schema,
+4
View File
@@ -6,6 +6,8 @@ export type ItemQtyMap = {
export type ItemSerialNumbers = { [item: string]: string };
export type ItemGroupMap = Record<string, string>;
export type DiscountType = 'percent' | 'amount';
export const modalNames = [
@@ -26,6 +28,7 @@ export type PosEmits =
| 'addItem'
| 'toggleView'
| 'toggleModal'
| 'setItemGroup'
| 'setPaidAmount'
| 'setPaymentMethod'
| 'setCouponsCount'
@@ -50,4 +53,5 @@ export interface POSItem {
unit: string;
hasBatch: boolean;
hasSerialNumber: boolean;
itemGroup?: string;
}
+20
View File
@@ -103,6 +103,18 @@
"
@change="(item: string) => emitEvent('handleItemSearch', item)"
/>
<Link
:df="{
label: t`Filter by Group`,
fieldtype: 'Link',
fieldname: 'itemGroup',
target: 'ItemGroup',
}"
:border="true"
:value="selectedItemGroup"
@change="(group: string) => emitEvent('setItemGroup',group)"
/>
</div>
<ItemsTable
@@ -348,6 +360,7 @@ import { Item } from 'models/baseModels/Item/Item';
import CouponCodeModal from './CouponCodeModal.vue';
import POSQuickActions from './POSQuickActions.vue';
import { PosEmits } from 'src/components/POS/types';
import Link from 'src/components/Controls/Link.vue';
import SavedInvoiceModal from './SavedInvoiceModal.vue';
import OpenPOSShiftModal from './OpenPOSShiftModal.vue';
import ClosePOSShiftModal from './ClosePOSShiftModal.vue';
@@ -366,6 +379,7 @@ import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/Applied
export default defineComponent({
name: 'ClassicPOS',
components: {
Link,
Button,
ItemsGrid,
AlertModal,
@@ -411,6 +425,10 @@ export default defineComponent({
type: String,
default: '',
},
selectedItemGroup: {
type: String,
default: '',
},
loyaltyProgram: {
type: String,
default: '',
@@ -442,6 +460,7 @@ export default defineComponent({
'toggleModal',
'setCustomer',
'clearValues',
'setItemGroup',
'setPaidAmount',
'setCouponsCount',
'routeToSinvList',
@@ -460,6 +479,7 @@ export default defineComponent({
],
data() {
return {
itemGroupFilter: '',
additionalDiscounts: fyo.pesa(0),
};
},
+12 -1
View File
@@ -19,6 +19,7 @@
:open-alert-modal="openAlertModal"
:default-customer="defaultCustomer"
:item-search-term="itemSearchTerm"
:selected-item-group="selectedItemGroup"
:is-pos-shift-open="isPosShiftOpen"
:items="(items as [] as POSItem[])"
:sinv-doc="(sinvDoc as SalesInvoice)"
@@ -40,6 +41,7 @@
@clear-values="clearValues"
@set-customer="setCustomer"
@toggle-modal="toggleModal"
@set-item-group="setItemGroup"
@handle-item-search="handleItemSearch"
@set-paid-amount="setPaidAmount"
@set-payment-method="setPaymentMethod"
@@ -214,6 +216,7 @@ export default defineComponent({
appliedCoupons: [] as AppliedCouponCodes[],
itemSearchTerm: '',
selectedItemGroup: '',
paymentMethod: undefined as string | undefined,
transferRefNo: undefined as string | undefined,
defaultCustomer: undefined as string | undefined,
@@ -458,6 +461,10 @@ export default defineComponent({
await this.afterSync();
},
async setItemGroup(itemGroupName: string) {
this.selectedItemGroup = itemGroupName;
await this.setItems();
},
async setItems() {
const filters: Record<string, boolean> = {};
const itemVisibility = this.fyo.singles.POSSettings?.itemVisibility;
@@ -470,9 +477,13 @@ export default defineComponent({
filters.trackItem = false;
}
const itemGroupfilter = this.selectedItemGroup
? { itemGroup: this.selectedItemGroup }
: undefined;
const items = (await fyo.db.getAll(ModelNameEnum.Item, {
fields: [],
filters,
filters: itemGroupfilter,
})) as Item[];
this.items = [] as POSItem[];