mirror of
https://github.com/frappe/books.git
synced 2024-12-23 03:19:01 +00:00
refactor: Item and Address models to ts Doc subcls
This commit is contained in:
parent
cb54100db4
commit
781c1d70e9
@ -1,49 +1,10 @@
|
|||||||
import { showMessageDialog } from '@/utils';
|
import { showMessageDialog } from '@/utils';
|
||||||
import frappe, { t } from 'frappe';
|
import frappe, { t } from 'frappe';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { stateCodeMap } from '../regional/in';
|
||||||
import { exportCsv, saveExportData } from '../reports/commonExporter';
|
import { exportCsv, saveExportData } from '../reports/commonExporter';
|
||||||
import { getSavePath } from '../src/utils';
|
import { getSavePath } from '../src/utils';
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const stateCodeMap = {
|
|
||||||
'JAMMU AND KASHMIR': '1',
|
|
||||||
'HIMACHAL PRADESH': '2',
|
|
||||||
'PUNJAB': '3',
|
|
||||||
'CHANDIGARH': '4',
|
|
||||||
'UTTARAKHAND': '5',
|
|
||||||
'HARYANA': '6',
|
|
||||||
'DELHI': '7',
|
|
||||||
'RAJASTHAN': '8',
|
|
||||||
'UTTAR PRADESH': '9',
|
|
||||||
'BIHAR': '10',
|
|
||||||
'SIKKIM': '11',
|
|
||||||
'ARUNACHAL PRADESH': '12',
|
|
||||||
'NAGALAND': '13',
|
|
||||||
'MANIPUR': '14',
|
|
||||||
'MIZORAM': '15',
|
|
||||||
'TRIPURA': '16',
|
|
||||||
'MEGHALAYA': '17',
|
|
||||||
'ASSAM': '18',
|
|
||||||
'WEST BENGAL': '19',
|
|
||||||
'JHARKHAND': '20',
|
|
||||||
'ODISHA': '21',
|
|
||||||
'CHATTISGARH': '22',
|
|
||||||
'MADHYA PRADESH': '23',
|
|
||||||
'GUJARAT': '24',
|
|
||||||
'DADRA AND NAGAR HAVELI AND DAMAN AND DIU': '26',
|
|
||||||
'MAHARASHTRA': '27',
|
|
||||||
'KARNATAKA': '29',
|
|
||||||
'GOA': '30',
|
|
||||||
'LAKSHADWEEP': '31',
|
|
||||||
'KERALA': '32',
|
|
||||||
'TAMIL NADU': '33',
|
|
||||||
'PUDUCHERRY': '34',
|
|
||||||
'ANDAMAN AND NICOBAR ISLANDS': '35',
|
|
||||||
'TELANGANA': '36',
|
|
||||||
'ANDHRA PRADESH': '37',
|
|
||||||
'LADAKH': '38',
|
|
||||||
};
|
|
||||||
|
|
||||||
const GST = {
|
const GST = {
|
||||||
'GST-0': 0,
|
'GST-0': 0,
|
||||||
'GST-0.25': 0.25,
|
'GST-0.25': 0.25,
|
||||||
|
@ -27,8 +27,10 @@ import {
|
|||||||
} from './helpers';
|
} from './helpers';
|
||||||
import { setName } from './naming';
|
import { setName } from './naming';
|
||||||
import {
|
import {
|
||||||
|
Action,
|
||||||
DefaultMap,
|
DefaultMap,
|
||||||
DependsOnMap,
|
DependsOnMap,
|
||||||
|
EmptyMessageMap,
|
||||||
FiltersMap,
|
FiltersMap,
|
||||||
FormulaMap,
|
FormulaMap,
|
||||||
ListsMap,
|
ListsMap,
|
||||||
@ -504,7 +506,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getValueFromFormula(field: Field, doc: Doc) {
|
async getValueFromFormula(field: Field, doc: Doc) {
|
||||||
let value: Doc[] | DocValue;
|
let value: Doc[] | DocValue | undefined;
|
||||||
|
|
||||||
const formula = doc.formulas[field.fieldtype];
|
const formula = doc.formulas[field.fieldtype];
|
||||||
if (formula === undefined) {
|
if (formula === undefined) {
|
||||||
@ -704,6 +706,9 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
|
|
||||||
static lists: ListsMap = {};
|
static lists: ListsMap = {};
|
||||||
static filters: FiltersMap = {};
|
static filters: FiltersMap = {};
|
||||||
|
static emptyMessages: EmptyMessageMap = {};
|
||||||
static listSettings: ListViewSettings = {};
|
static listSettings: ListViewSettings = {};
|
||||||
static treeSettings?: TreeViewSettings;
|
static treeSettings?: TreeViewSettings;
|
||||||
|
|
||||||
|
static actions: Action[] = [];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { DocValue } from 'frappe/core/types';
|
import { DocValue } from 'frappe/core/types';
|
||||||
import { FieldType } from 'schemas/types';
|
import { FieldType } from 'schemas/types';
|
||||||
import { QueryFilter } from 'utils/db/types';
|
import { QueryFilter } from 'utils/db/types';
|
||||||
|
import { Router } from 'vue-router';
|
||||||
import Doc from './doc';
|
import Doc from './doc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,9 +40,18 @@ export type DocMap = Record<string, Doc | undefined>;
|
|||||||
export type FilterFunction = (doc: Doc) => QueryFilter;
|
export type FilterFunction = (doc: Doc) => QueryFilter;
|
||||||
export type FiltersMap = Record<string, FilterFunction>;
|
export type FiltersMap = Record<string, FilterFunction>;
|
||||||
|
|
||||||
export type ListFunction = () => string[];
|
export type EmptyMessageFunction = (doc: Doc) => string;
|
||||||
|
export type EmptyMessageMap = Record<string, EmptyMessageFunction>;
|
||||||
|
|
||||||
|
export type ListFunction = (doc?: Doc) => string[];
|
||||||
export type ListsMap = Record<string, ListFunction>;
|
export type ListsMap = Record<string, ListFunction>;
|
||||||
|
|
||||||
|
export interface Action {
|
||||||
|
label: string;
|
||||||
|
condition: (doc: Doc) => boolean;
|
||||||
|
action: (doc: Doc, router: Router) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ColumnConfig {
|
export interface ColumnConfig {
|
||||||
label: string;
|
label: string;
|
||||||
fieldtype: FieldType;
|
fieldtype: FieldType;
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
import { t } from 'frappe';
|
|
||||||
import { stateCodeMap } from '../../../accounting/gst';
|
|
||||||
import countryList from '../../../fixtures/countryInfo.json';
|
|
||||||
import { titleCase } from '../../../src/utils';
|
|
||||||
|
|
||||||
function getStates(doc) {
|
|
||||||
switch (doc.country) {
|
|
||||||
case 'India':
|
|
||||||
return Object.keys(stateCodeMap).map(titleCase).sort();
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Address',
|
|
||||||
doctype: 'DocType',
|
|
||||||
regional: 1,
|
|
||||||
isSingle: 0,
|
|
||||||
keywordFields: [
|
|
||||||
'addressLine1',
|
|
||||||
'addressLine2',
|
|
||||||
'city',
|
|
||||||
'state',
|
|
||||||
'country',
|
|
||||||
'postalCode',
|
|
||||||
],
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
fieldname: 'addressLine1',
|
|
||||||
label: t`Address Line 1`,
|
|
||||||
placeholder: t`Address Line 1`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
required: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'addressLine2',
|
|
||||||
label: t`Address Line 2`,
|
|
||||||
placeholder: t`Address Line 2`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'city',
|
|
||||||
label: t`City / Town`,
|
|
||||||
placeholder: t`City / Town`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
required: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'state',
|
|
||||||
label: t`State`,
|
|
||||||
placeholder: t`State`,
|
|
||||||
fieldtype: 'AutoComplete',
|
|
||||||
emptyMessage: (doc) => {
|
|
||||||
if (doc.country) {
|
|
||||||
return 'Enter State';
|
|
||||||
}
|
|
||||||
return 'Enter Country to load States';
|
|
||||||
},
|
|
||||||
getList: getStates,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'country',
|
|
||||||
label: t`Country`,
|
|
||||||
placeholder: t`Country`,
|
|
||||||
fieldtype: 'AutoComplete',
|
|
||||||
getList: () => Object.keys(countryList).sort(),
|
|
||||||
required: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'postalCode',
|
|
||||||
label: t`Postal Code`,
|
|
||||||
placeholder: t`Postal Code`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'emailAddress',
|
|
||||||
label: t`Email Address`,
|
|
||||||
placeholder: t`Email Address`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'phone',
|
|
||||||
label: t`Phone`,
|
|
||||||
placeholder: t`Phone`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'fax',
|
|
||||||
label: t`Fax`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'addressDisplay',
|
|
||||||
fieldtype: 'Text',
|
|
||||||
label: t`Address Display`,
|
|
||||||
readOnly: true,
|
|
||||||
formula: (doc) => {
|
|
||||||
return [
|
|
||||||
doc.addressLine1,
|
|
||||||
doc.addressLine2,
|
|
||||||
doc.city,
|
|
||||||
doc.state,
|
|
||||||
doc.country,
|
|
||||||
doc.postalCode,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(', ');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
quickEditFields: [
|
|
||||||
'addressLine1',
|
|
||||||
'addressLine2',
|
|
||||||
'city',
|
|
||||||
'state',
|
|
||||||
'country',
|
|
||||||
'postalCode',
|
|
||||||
],
|
|
||||||
inlineEditDisplayField: 'addressDisplay',
|
|
||||||
};
|
|
48
models/baseModels/Address/Address.ts
Normal file
48
models/baseModels/Address/Address.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import frappe from 'frappe';
|
||||||
|
import Doc from 'frappe/model/doc';
|
||||||
|
import { EmptyMessageMap, FormulaMap, ListsMap } from 'frappe/model/types';
|
||||||
|
import { stateCodeMap } from 'regional/in';
|
||||||
|
import { titleCase } from 'utils';
|
||||||
|
import countryInfo from '../../../fixtures/countryInfo.json';
|
||||||
|
|
||||||
|
export class Address extends Doc {
|
||||||
|
formulas: FormulaMap = {
|
||||||
|
addressDisplay: async () => {
|
||||||
|
return [
|
||||||
|
this.addressLine1,
|
||||||
|
this.addressLine2,
|
||||||
|
this.city,
|
||||||
|
this.state,
|
||||||
|
this.country,
|
||||||
|
this.postalCode,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(', ');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static lists: ListsMap = {
|
||||||
|
state(doc?: Doc) {
|
||||||
|
const country = doc?.country as string | undefined;
|
||||||
|
switch (country) {
|
||||||
|
case 'India':
|
||||||
|
return Object.keys(stateCodeMap).map(titleCase).sort();
|
||||||
|
default:
|
||||||
|
return [] as string[];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
country() {
|
||||||
|
return Object.keys(countryInfo).sort();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static emptyMessages: EmptyMessageMap = {
|
||||||
|
state: (doc: Doc) => {
|
||||||
|
if (doc.country) {
|
||||||
|
return frappe.t`Enter State`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frappe.t`Enter Country to load States`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import { t } from 'frappe';
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import { stateCodeMap } from '../../../accounting/gst';
|
|
||||||
import { titleCase } from '../../../src/utils';
|
|
||||||
import AddressOriginal from './Address';
|
|
||||||
|
|
||||||
export default function getAugmentedAddress({ country }) {
|
|
||||||
const Address = cloneDeep(AddressOriginal);
|
|
||||||
if (!country) {
|
|
||||||
return Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
|
|
||||||
if (country === 'India') {
|
|
||||||
Address.fields = [
|
|
||||||
...Address.fields,
|
|
||||||
{
|
|
||||||
fieldname: 'pos',
|
|
||||||
label: t`Place of Supply`,
|
|
||||||
fieldtype: 'AutoComplete',
|
|
||||||
placeholder: t`Place of Supply`,
|
|
||||||
formula: (doc) => (stateList.includes(doc.state) ? doc.state : ''),
|
|
||||||
getList: () => stateList,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
Address.quickEditFields = [...Address.quickEditFields, 'pos'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Address;
|
|
||||||
}
|
|
@ -1,168 +0,0 @@
|
|||||||
import frappe, { t } from 'frappe';
|
|
||||||
|
|
||||||
const itemForMap = {
|
|
||||||
purchases: t`Purchases`,
|
|
||||||
sales: t`Sales`,
|
|
||||||
both: t`Both`,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Item',
|
|
||||||
label: t`Item`,
|
|
||||||
doctype: 'DocType',
|
|
||||||
isSingle: 0,
|
|
||||||
regional: 1,
|
|
||||||
keywordFields: ['name', 'description'],
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
fieldname: 'name',
|
|
||||||
label: t`Item Name`,
|
|
||||||
fieldtype: 'Data',
|
|
||||||
placeholder: t`Item Name`,
|
|
||||||
required: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'image',
|
|
||||||
label: t`Image`,
|
|
||||||
fieldtype: 'AttachImage',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'description',
|
|
||||||
label: t`Description`,
|
|
||||||
placeholder: t`Item Description`,
|
|
||||||
fieldtype: 'Text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'unit',
|
|
||||||
label: t`Unit Type`,
|
|
||||||
fieldtype: 'Select',
|
|
||||||
placeholder: t`Unit Type`,
|
|
||||||
default: 'Unit',
|
|
||||||
options: ['Unit', 'Kg', 'Gram', 'Hour', 'Day'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'itemType',
|
|
||||||
label: t`Type`,
|
|
||||||
placeholder: t`Type`,
|
|
||||||
fieldtype: 'Select',
|
|
||||||
default: 'Product',
|
|
||||||
options: ['Product', 'Service'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'for',
|
|
||||||
label: t`For`,
|
|
||||||
fieldtype: 'Select',
|
|
||||||
options: Object.keys(itemForMap),
|
|
||||||
map: itemForMap,
|
|
||||||
default: 'both',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'incomeAccount',
|
|
||||||
label: t`Income`,
|
|
||||||
fieldtype: 'Link',
|
|
||||||
target: 'Account',
|
|
||||||
placeholder: t`Income`,
|
|
||||||
required: 1,
|
|
||||||
disableCreation: true,
|
|
||||||
getFilters: () => {
|
|
||||||
return {
|
|
||||||
isGroup: 0,
|
|
||||||
rootType: 'Income',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
formulaDependsOn: ['itemType'],
|
|
||||||
async formula(doc) {
|
|
||||||
let accountName = 'Service';
|
|
||||||
if (doc.itemType === 'Product') {
|
|
||||||
accountName = 'Sales';
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountExists = await frappe.db.exists('Account', accountName);
|
|
||||||
return accountExists ? accountName : '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'expenseAccount',
|
|
||||||
label: t`Expense`,
|
|
||||||
fieldtype: 'Link',
|
|
||||||
target: 'Account',
|
|
||||||
placeholder: t`Expense`,
|
|
||||||
required: 1,
|
|
||||||
disableCreation: true,
|
|
||||||
getFilters: () => {
|
|
||||||
return {
|
|
||||||
isGroup: 0,
|
|
||||||
rootType: 'Expense',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
formulaDependsOn: ['itemType'],
|
|
||||||
async formula() {
|
|
||||||
const cogs = await frappe.db.getAllRaw('Account', {
|
|
||||||
filters: {
|
|
||||||
accountType: 'Cost of Goods Sold',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (cogs.length === 0) {
|
|
||||||
return '';
|
|
||||||
} else {
|
|
||||||
return cogs[0].name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'tax',
|
|
||||||
label: t`Tax`,
|
|
||||||
fieldtype: 'Link',
|
|
||||||
target: 'Tax',
|
|
||||||
placeholder: t`Tax`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldname: 'rate',
|
|
||||||
label: t`Rate`,
|
|
||||||
fieldtype: 'Currency',
|
|
||||||
validate(value) {
|
|
||||||
if (value.isNegative()) {
|
|
||||||
throw new frappe.errors.ValidationError(t`Rate can't be negative.`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
quickEditFields: [
|
|
||||||
'rate',
|
|
||||||
'unit',
|
|
||||||
'itemType',
|
|
||||||
'for',
|
|
||||||
'tax',
|
|
||||||
'description',
|
|
||||||
'incomeAccount',
|
|
||||||
'expenseAccount',
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: t`New Invoice`,
|
|
||||||
condition: (doc) => !doc.isNew(),
|
|
||||||
action: async (doc, router) => {
|
|
||||||
const invoice = await frappe.getEmptyDoc('SalesInvoice');
|
|
||||||
invoice.append('items', {
|
|
||||||
item: doc.name,
|
|
||||||
rate: doc.rate,
|
|
||||||
tax: doc.tax,
|
|
||||||
});
|
|
||||||
router.push(`/edit/SalesInvoice/${invoice.name}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t`New Bill`,
|
|
||||||
condition: (doc) => !doc.isNew(),
|
|
||||||
action: async (doc, router) => {
|
|
||||||
const invoice = await frappe.getEmptyDoc('PurchaseInvoice');
|
|
||||||
invoice.append('items', {
|
|
||||||
item: doc.name,
|
|
||||||
rate: doc.rate,
|
|
||||||
tax: doc.tax,
|
|
||||||
});
|
|
||||||
router.push(`/edit/PurchaseInvoice/${invoice.name}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
98
models/baseModels/Item/Item.ts
Normal file
98
models/baseModels/Item/Item.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import frappe from 'frappe';
|
||||||
|
import { DocValue } from 'frappe/core/types';
|
||||||
|
import Doc from 'frappe/model/doc';
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
DependsOnMap,
|
||||||
|
FiltersMap,
|
||||||
|
FormulaMap,
|
||||||
|
ListViewSettings,
|
||||||
|
ValidationMap,
|
||||||
|
} from 'frappe/model/types';
|
||||||
|
import Money from 'pesa/dist/types/src/money';
|
||||||
|
|
||||||
|
export class Item extends Doc {
|
||||||
|
formulas: FormulaMap = {
|
||||||
|
incomeAccount: async () => {
|
||||||
|
let accountName = 'Service';
|
||||||
|
if (this.itemType === 'Product') {
|
||||||
|
accountName = 'Sales';
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountExists = await frappe.db.exists('Account', accountName);
|
||||||
|
return accountExists ? accountName : '';
|
||||||
|
},
|
||||||
|
expenseAccount: async () => {
|
||||||
|
const cogs = await frappe.db.getAllRaw('Account', {
|
||||||
|
filters: {
|
||||||
|
accountType: 'Cost of Goods Sold',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cogs.length === 0) {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
return cogs[0].name as string;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static filters: FiltersMap = {
|
||||||
|
incomeAccount: () => ({
|
||||||
|
isGroup: false,
|
||||||
|
rootType: 'Income',
|
||||||
|
}),
|
||||||
|
expenseAccount: () => ({
|
||||||
|
isGroup: false,
|
||||||
|
rootType: 'Expense',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
dependsOn: DependsOnMap = {
|
||||||
|
incomeAccount: ['itemType'],
|
||||||
|
expenseAccount: ['itemType'],
|
||||||
|
};
|
||||||
|
|
||||||
|
validations: ValidationMap = {
|
||||||
|
rate: async (value: DocValue) => {
|
||||||
|
if ((value as Money).isNegative()) {
|
||||||
|
throw new frappe.errors.ValidationError(
|
||||||
|
frappe.t`Rate can't be negative.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
actions: Action[] = [
|
||||||
|
{
|
||||||
|
label: frappe.t`New Invoice`,
|
||||||
|
condition: (doc) => !doc.isNew,
|
||||||
|
action: async (doc, router) => {
|
||||||
|
const invoice = await frappe.doc.getEmptyDoc('SalesInvoice');
|
||||||
|
invoice.append('items', {
|
||||||
|
item: doc.name as string,
|
||||||
|
rate: doc.rate as Money,
|
||||||
|
tax: doc.tax as string,
|
||||||
|
});
|
||||||
|
router.push(`/edit/SalesInvoice/${invoice.name}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: frappe.t`New Bill`,
|
||||||
|
condition: (doc) => !doc.isNew,
|
||||||
|
action: async (doc, router) => {
|
||||||
|
const invoice = await frappe.doc.getEmptyDoc('PurchaseInvoice');
|
||||||
|
invoice.append('items', {
|
||||||
|
item: doc.name as string,
|
||||||
|
rate: doc.rate as Money,
|
||||||
|
tax: doc.tax as string,
|
||||||
|
});
|
||||||
|
router.push(`/edit/PurchaseInvoice/${invoice.name}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
listSettings: ListViewSettings = {
|
||||||
|
columns: ['name', 'unit', 'tax', 'rate'],
|
||||||
|
};
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
import { t } from 'frappe';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
doctype: 'Item',
|
|
||||||
title: t`Items`,
|
|
||||||
columns: ['name', 'unit', 'tax', 'rate'],
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
import { t } from 'frappe';
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import ItemOriginal from './Item';
|
|
||||||
|
|
||||||
export default function getAugmentedItem({ country }) {
|
|
||||||
const Item = cloneDeep(ItemOriginal);
|
|
||||||
if (!country) {
|
|
||||||
return Item;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (country === 'India') {
|
|
||||||
const nameFieldIndex = Item.fields.findIndex((i) => i.fieldname === 'name');
|
|
||||||
|
|
||||||
Item.fields = [
|
|
||||||
...Item.fields.slice(0, nameFieldIndex + 1),
|
|
||||||
{
|
|
||||||
fieldname: 'hsnCode',
|
|
||||||
label: t`HSN/SAC`,
|
|
||||||
fieldtype: 'Int',
|
|
||||||
placeholder: t`HSN/SAC Code`,
|
|
||||||
},
|
|
||||||
...Item.fields.slice(nameFieldIndex + 1, Item.fields.length),
|
|
||||||
];
|
|
||||||
|
|
||||||
Item.quickEditFields.unshift('hsnCode');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Item;
|
|
||||||
}
|
|
37
models/regionalModels/in/Address.ts
Normal file
37
models/regionalModels/in/Address.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { FormulaMap, ListsMap } from 'frappe/model/types';
|
||||||
|
import { Address as BaseAddress } from 'models/baseModels/Address/Address';
|
||||||
|
import { stateCodeMap } from 'regional/in';
|
||||||
|
import { titleCase } from 'utils';
|
||||||
|
|
||||||
|
export class Address extends BaseAddress {
|
||||||
|
formulas: FormulaMap = {
|
||||||
|
addressDisplay: async () => {
|
||||||
|
return [
|
||||||
|
this.addressLine1,
|
||||||
|
this.addressLine2,
|
||||||
|
this.city,
|
||||||
|
this.state,
|
||||||
|
this.country,
|
||||||
|
this.postalCode,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(', ');
|
||||||
|
},
|
||||||
|
|
||||||
|
pos: async () => {
|
||||||
|
const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
|
||||||
|
const state = this.state as string;
|
||||||
|
if (stateList.includes(state)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static lists: ListsMap = {
|
||||||
|
...BaseAddress.lists,
|
||||||
|
pos: () => {
|
||||||
|
return Object.keys(stateCodeMap).map(titleCase).sort();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
39
regional/in.ts
Normal file
39
regional/in.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// prettier-ignore
|
||||||
|
export const stateCodeMap = {
|
||||||
|
'JAMMU AND KASHMIR': '1',
|
||||||
|
'HIMACHAL PRADESH': '2',
|
||||||
|
'PUNJAB': '3',
|
||||||
|
'CHANDIGARH': '4',
|
||||||
|
'UTTARAKHAND': '5',
|
||||||
|
'HARYANA': '6',
|
||||||
|
'DELHI': '7',
|
||||||
|
'RAJASTHAN': '8',
|
||||||
|
'UTTAR PRADESH': '9',
|
||||||
|
'BIHAR': '10',
|
||||||
|
'SIKKIM': '11',
|
||||||
|
'ARUNACHAL PRADESH': '12',
|
||||||
|
'NAGALAND': '13',
|
||||||
|
'MANIPUR': '14',
|
||||||
|
'MIZORAM': '15',
|
||||||
|
'TRIPURA': '16',
|
||||||
|
'MEGHALAYA': '17',
|
||||||
|
'ASSAM': '18',
|
||||||
|
'WEST BENGAL': '19',
|
||||||
|
'JHARKHAND': '20',
|
||||||
|
'ODISHA': '21',
|
||||||
|
'CHATTISGARH': '22',
|
||||||
|
'MADHYA PRADESH': '23',
|
||||||
|
'GUJARAT': '24',
|
||||||
|
'DADRA AND NAGAR HAVELI AND DAMAN AND DIU': '26',
|
||||||
|
'MAHARASHTRA': '27',
|
||||||
|
'KARNATAKA': '29',
|
||||||
|
'GOA': '30',
|
||||||
|
'LAKSHADWEEP': '31',
|
||||||
|
'KERALA': '32',
|
||||||
|
'TAMIL NADU': '33',
|
||||||
|
'PUDUCHERRY': '34',
|
||||||
|
'ANDAMAN AND NICOBAR ISLANDS': '35',
|
||||||
|
'TELANGANA': '36',
|
||||||
|
'ANDHRA PRADESH': '37',
|
||||||
|
'LADAKH': '38',
|
||||||
|
};
|
@ -83,5 +83,6 @@
|
|||||||
"state",
|
"state",
|
||||||
"country",
|
"country",
|
||||||
"postalCode"
|
"postalCode"
|
||||||
]
|
],
|
||||||
|
"inlineEditDisplayField": "addressDisplay"
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,7 @@ export interface Schema {
|
|||||||
keywordFields?: string[]; // Used to get fields that are to be used for search.
|
keywordFields?: string[]; // Used to get fields that are to be used for search.
|
||||||
quickEditFields?: string[]; // Used to get fields for the quickEditForm
|
quickEditFields?: string[]; // Used to get fields for the quickEditForm
|
||||||
treeSettings?: TreeSettings; // Used to determine root nodes
|
treeSettings?: TreeSettings; // Used to determine root nodes
|
||||||
|
inlineEditDisplayField?:string,// Display field if inline editable
|
||||||
naming?: Naming; // Used for assigning name, default is 'random' else 'numberSeries' if present
|
naming?: Naming; // Used for assigning name, default is 'random' else 'numberSeries' if present
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
src/utils.js
13
src/utils.js
@ -423,19 +423,6 @@ export function showToast(props) {
|
|||||||
replaceAndAppendMount(toast, 'toast-target');
|
replaceAndAppendMount(toast, 'toast-target');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function titleCase(phrase) {
|
|
||||||
return phrase
|
|
||||||
.split(' ')
|
|
||||||
.map((word) => {
|
|
||||||
const wordLower = word.toLowerCase();
|
|
||||||
if (['and', 'an', 'a', 'from', 'by', 'on'].includes(wordLower)) {
|
|
||||||
return wordLower;
|
|
||||||
}
|
|
||||||
return wordLower[0].toUpperCase() + wordLower.slice(1);
|
|
||||||
})
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getIsSetupComplete() {
|
export async function getIsSetupComplete() {
|
||||||
try {
|
try {
|
||||||
const { setupComplete } = await frappe.getSingle('AccountingSettings');
|
const { setupComplete } = await frappe.getSingle('AccountingSettings');
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"],
|
||||||
"schemas/*": ["schemas/*"],
|
"schemas/*": ["schemas/*"],
|
||||||
"backend/*": ["backend/*"],
|
"backend/*": ["backend/*"],
|
||||||
|
"regional/*": ["regional/*"],
|
||||||
"utils/*": ["utils/*"]
|
"utils/*": ["utils/*"]
|
||||||
},
|
},
|
||||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||||
|
@ -55,3 +55,16 @@ export function getListFromMap<T>(map: Record<string, T>): T[] {
|
|||||||
export function getIsNullOrUndef(value: unknown): boolean {
|
export function getIsNullOrUndef(value: unknown): boolean {
|
||||||
return value === null || value === undefined;
|
return value === null || value === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function titleCase(phrase: string): string {
|
||||||
|
return phrase
|
||||||
|
.split(' ')
|
||||||
|
.map((word) => {
|
||||||
|
const wordLower = word.toLowerCase();
|
||||||
|
if (['and', 'an', 'a', 'from', 'by', 'on'].includes(wordLower)) {
|
||||||
|
return wordLower;
|
||||||
|
}
|
||||||
|
return wordLower[0].toUpperCase() + wordLower.slice(1);
|
||||||
|
})
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
@ -44,6 +44,7 @@ module.exports = {
|
|||||||
schemas: path.resolve(__dirname, './schemas'),
|
schemas: path.resolve(__dirname, './schemas'),
|
||||||
backend: path.resolve(__dirname, './backend'),
|
backend: path.resolve(__dirname, './backend'),
|
||||||
utils: path.resolve(__dirname, './utils'),
|
utils: path.resolve(__dirname, './utils'),
|
||||||
|
regional: path.resolve(__dirname, './regional'),
|
||||||
});
|
});
|
||||||
|
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
|
Loading…
Reference in New Issue
Block a user