mirror of
https://github.com/frappe/books.git
synced 2025-01-05 08:02:15 +00:00
incr: improve stockmovement ux
- hide inventory defaults until enabled
This commit is contained in:
parent
8b04b5b2ab
commit
b5f8e49299
@ -233,6 +233,7 @@ export class Fyo {
|
|||||||
instanceId: '',
|
instanceId: '',
|
||||||
deviceId: '',
|
deviceId: '',
|
||||||
openCount: -1,
|
openCount: -1,
|
||||||
|
appFlags: {} as Record<string, boolean>,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { strictEqual } from 'assert';
|
import { strictEqual } from 'assert';
|
||||||
|
import { assertThrows } from 'backend/database/tests/helpers';
|
||||||
import Observable from 'fyo/utils/observable';
|
import Observable from 'fyo/utils/observable';
|
||||||
import test from 'tape';
|
import test from 'tape';
|
||||||
|
|
||||||
@ -30,55 +31,69 @@ const listenerBOnce = (value: number) => {
|
|||||||
strictEqual(params.b, value, 'listenerBOnce');
|
strictEqual(params.b, value, 'listenerBOnce');
|
||||||
};
|
};
|
||||||
|
|
||||||
test('set A One', function (t) {
|
test('set A One', (t) => {
|
||||||
t.equal(obs.hasListener(ObsEvent.A), false, 'pre');
|
t.equal(obs.hasListener(ObsEvent.A), false, 'pre');
|
||||||
|
|
||||||
obs.once(ObsEvent.A, listenerAOnce);
|
obs.once(ObsEvent.A, listenerAOnce);
|
||||||
t.equal(obs.hasListener(ObsEvent.A), true, 'non specific');
|
t.equal(obs.hasListener(ObsEvent.A), true, 'non specific');
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAOnce), true, 'specific once');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAOnce), true, 'specific once');
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), false, 'specific every');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), false, 'specific every');
|
||||||
t.end()
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('set A Two', function (t) {
|
test('set A Two', (t) => {
|
||||||
obs.on(ObsEvent.A, listenerAEvery);
|
obs.on(ObsEvent.A, listenerAEvery);
|
||||||
t.equal(obs.hasListener(ObsEvent.A), true, 'non specific');
|
t.equal(obs.hasListener(ObsEvent.A), true, 'non specific');
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAOnce), true, 'specific once');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAOnce), true, 'specific once');
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), true, 'specific every');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), true, 'specific every');
|
||||||
t.end()
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('set B', function (t) {
|
test('set B', (t) => {
|
||||||
t.equal(obs.hasListener(ObsEvent.B), false, 'pre');
|
t.equal(obs.hasListener(ObsEvent.B), false, 'pre');
|
||||||
|
|
||||||
obs.once(ObsEvent.B, listenerBOnce);
|
obs.once(ObsEvent.B, listenerBOnce);
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerBOnce), false, 'specific false');
|
t.equal(obs.hasListener(ObsEvent.A, listenerBOnce), false, 'specific false');
|
||||||
t.equal(obs.hasListener(ObsEvent.B, listenerBOnce), true, 'specific true');
|
t.equal(obs.hasListener(ObsEvent.B, listenerBOnce), true, 'specific true');
|
||||||
t.end()
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trigger A 0', async function (t) {
|
test('trigger A 0', async (t) => {
|
||||||
await obs.trigger(ObsEvent.A, params.aOne);
|
await obs.trigger(ObsEvent.A, params.aOne);
|
||||||
t.equal(obs.hasListener(ObsEvent.A), true, 'non specific');
|
t.equal(obs.hasListener(ObsEvent.A), true, 'non specific');
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAOnce), false, 'specific');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAOnce), false, 'specific');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trigger A 1', async function (t) {
|
test('trigger A 1', async (t) => {
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), true, 'specific pre');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), true, 'specific pre');
|
||||||
await obs.trigger(ObsEvent.A, params.aTwo);
|
await obs.trigger(ObsEvent.A, params.aTwo);
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), true, 'specific post');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), true, 'specific post');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('trigger B', async function (t) {
|
test('trigger B', async (t) => {
|
||||||
t.equal(obs.hasListener(ObsEvent.B, listenerBOnce), true, 'specific pre');
|
t.equal(obs.hasListener(ObsEvent.B, listenerBOnce), true, 'specific pre');
|
||||||
await obs.trigger(ObsEvent.B, params.b);
|
await obs.trigger(ObsEvent.B, params.b);
|
||||||
t.equal(obs.hasListener(ObsEvent.B, listenerBOnce), false, 'specific post');
|
t.equal(obs.hasListener(ObsEvent.B, listenerBOnce), false, 'specific post');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('remove A', async function (t) {
|
test('remove A', async (t) => {
|
||||||
obs.off(ObsEvent.A, listenerAEvery);
|
obs.off(ObsEvent.A, listenerAEvery);
|
||||||
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), false, 'specific pre');
|
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), false, 'specific pre');
|
||||||
|
|
||||||
t.equal(counter, 2, 'incorrect counter');
|
t.equal(counter, 2, 'incorrect counter');
|
||||||
await obs.trigger(ObsEvent.A, 777);
|
await obs.trigger(ObsEvent.A, 777);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('observable trigger error propagation', async (t) => {
|
||||||
|
const obs = new Observable();
|
||||||
|
obs.on('testOne', () => {
|
||||||
|
throw new Error('stuff');
|
||||||
|
});
|
||||||
|
|
||||||
|
await assertThrows(async () => {
|
||||||
|
await obs.trigger('testOne');
|
||||||
|
t.ok(false, 'trigger should throw error');
|
||||||
|
});
|
||||||
|
|
||||||
|
t.ok(true, 'assert throws success');
|
||||||
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { FiltersMap } from 'fyo/model/types';
|
import { FiltersMap, HiddenMap } from 'fyo/model/types';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
|
||||||
export class Defaults extends Doc {
|
export class Defaults extends Doc {
|
||||||
@ -42,6 +42,18 @@ export class Defaults extends Doc {
|
|||||||
|
|
||||||
static filters: FiltersMap = this.commonFilters;
|
static filters: FiltersMap = this.commonFilters;
|
||||||
static createFilters: FiltersMap = this.commonFilters;
|
static createFilters: FiltersMap = this.commonFilters;
|
||||||
|
|
||||||
|
hideInventoryDefaults(): boolean {
|
||||||
|
return !this.fyo.store.appFlags.getIsInventoryEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden: HiddenMap = {
|
||||||
|
stockMovementNumberSeries: this.hideInventoryDefaults.bind(this),
|
||||||
|
shipmentNumberSeries: this.hideInventoryDefaults.bind(this),
|
||||||
|
purchaseReceiptNumberSeries: this.hideInventoryDefaults.bind(this),
|
||||||
|
shipmentTerms: this.hideInventoryDefaults.bind(this),
|
||||||
|
purchaseReceiptTerms: this.hideInventoryDefaults.bind(this),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const numberSeriesDefaultsMap: Record<
|
export const numberSeriesDefaultsMap: Record<
|
||||||
|
@ -10,15 +10,13 @@ import {
|
|||||||
HiddenMap,
|
HiddenMap,
|
||||||
ListViewSettings,
|
ListViewSettings,
|
||||||
RequiredMap,
|
RequiredMap,
|
||||||
ValidationMap
|
ValidationMap,
|
||||||
} from 'fyo/model/types';
|
} from 'fyo/model/types';
|
||||||
import { NotFoundError, ValidationError } from 'fyo/utils/errors';
|
import { NotFoundError, ValidationError } from 'fyo/utils/errors';
|
||||||
import {
|
import {
|
||||||
getDocStatus,
|
getDocStatusListColumn,
|
||||||
getLedgerLinkAction,
|
getLedgerLinkAction,
|
||||||
getNumberSeries,
|
getNumberSeries,
|
||||||
getStatusMap,
|
|
||||||
statusColor
|
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||||
import { Transactional } from 'models/Transactional/Transactional';
|
import { Transactional } from 'models/Transactional/Transactional';
|
||||||
@ -619,27 +617,7 @@ export class Payment extends Transactional {
|
|||||||
|
|
||||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
columns: [
|
columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'],
|
||||||
'name',
|
|
||||||
{
|
|
||||||
label: t`Status`,
|
|
||||||
fieldname: 'status',
|
|
||||||
fieldtype: 'Select',
|
|
||||||
size: 'small',
|
|
||||||
render(doc) {
|
|
||||||
const status = getDocStatus(doc);
|
|
||||||
const color = statusColor[status] ?? 'gray';
|
|
||||||
const label = getStatusMap()[status];
|
|
||||||
|
|
||||||
return {
|
|
||||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'party',
|
|
||||||
'date',
|
|
||||||
'amount',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,3 +267,21 @@ export function getNumberSeries(schemaName: string, fyo: Fyo) {
|
|||||||
const value = defaults?.[numberSeriesKey] as string | undefined;
|
const value = defaults?.[numberSeriesKey] as string | undefined;
|
||||||
return value ?? (field?.default as string | undefined);
|
return value ?? (field?.default as string | undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDocStatusListColumn(): ColumnConfig {
|
||||||
|
return {
|
||||||
|
label: t`Status`,
|
||||||
|
fieldname: 'status',
|
||||||
|
fieldtype: 'Select',
|
||||||
|
size: 'small',
|
||||||
|
render(doc) {
|
||||||
|
const status = getDocStatus(doc);
|
||||||
|
const color = statusColor[status] ?? 'gray';
|
||||||
|
const label = getStatusMap()[status];
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -25,6 +25,13 @@ export class StockManager {
|
|||||||
this.fyo = fyo;
|
this.fyo = fyo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validateTransfers(transferDetails: SMTransferDetails[]) {
|
||||||
|
const detailsList = transferDetails.map((d) => this.#getSMIDetails(d));
|
||||||
|
for (const details of detailsList) {
|
||||||
|
await this.#validate(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createTransfers(transferDetails: SMTransferDetails[]) {
|
async createTransfers(transferDetails: SMTransferDetails[]) {
|
||||||
const detailsList = transferDetails.map((d) => this.#getSMIDetails(d));
|
const detailsList = transferDetails.map((d) => this.#getSMIDetails(d));
|
||||||
for (const details of detailsList) {
|
for (const details of detailsList) {
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
FormulaMap,
|
FormulaMap,
|
||||||
ListViewSettings
|
ListViewSettings
|
||||||
} from 'fyo/model/types';
|
} from 'fyo/model/types';
|
||||||
|
import { getDocStatusListColumn } from 'models/helpers';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { StockManager } from './StockManager';
|
import { StockManager } from './StockManager';
|
||||||
@ -40,15 +41,25 @@ export class StockMovement extends Doc {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return { columns: ['name', 'date', 'movementType'] };
|
return {
|
||||||
|
columns: ['name', getDocStatusListColumn(), 'date', 'movementType'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async beforeSubmit(): Promise<void> {
|
||||||
|
await super.beforeSubmit();
|
||||||
|
const transferDetails = this._getTransferDetails();
|
||||||
|
await this._getStockManager().validateTransfers(transferDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterSubmit(): Promise<void> {
|
async afterSubmit(): Promise<void> {
|
||||||
|
await super.afterSubmit();
|
||||||
const transferDetails = this._getTransferDetails();
|
const transferDetails = this._getTransferDetails();
|
||||||
await this._getStockManager().createTransfers(transferDetails);
|
await this._getStockManager().createTransfers(transferDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterCancel(): Promise<void> {
|
async afterCancel(): Promise<void> {
|
||||||
|
await super.afterCancel();
|
||||||
await this._getStockManager().cancelTransfers();
|
await this._getStockManager().cancelTransfers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import {
|
import {
|
||||||
FilterFunction,
|
|
||||||
FiltersMap,
|
FiltersMap,
|
||||||
FormulaMap,
|
FormulaMap,
|
||||||
|
ReadOnlyMap,
|
||||||
RequiredMap
|
RequiredMap
|
||||||
} from 'fyo/model/types';
|
} from 'fyo/model/types';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { QueryFilter } from 'utils/db/types';
|
import { locationFilter } from './helpers';
|
||||||
import { StockMovement } from './StockMovement';
|
import { StockMovement } from './StockMovement';
|
||||||
|
import { MovementType } from './types';
|
||||||
const locationFilter: FilterFunction = (doc: Doc) => {
|
|
||||||
const item = doc.item;
|
|
||||||
if (!doc.item) {
|
|
||||||
return { item: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { item: ['in', [null, item]] } as QueryFilter;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class StockMovementItem extends Doc {
|
export class StockMovementItem extends Doc {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -50,6 +42,20 @@ export class StockMovementItem extends Doc {
|
|||||||
formula: () => this.rate!.mul(this.quantity!),
|
formula: () => this.rate!.mul(this.quantity!),
|
||||||
dependsOn: ['item', 'rate', 'quantity'],
|
dependsOn: ['item', 'rate', 'quantity'],
|
||||||
},
|
},
|
||||||
|
fromLocation: {
|
||||||
|
formula: () => {
|
||||||
|
if (this.parentdoc?.movementType === MovementType.MaterialReceipt) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toLocation: {
|
||||||
|
formula: () => {
|
||||||
|
if (this.parentdoc?.movementType === MovementType.MaterialIssue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
required: RequiredMap = {
|
required: RequiredMap = {
|
||||||
@ -61,6 +67,13 @@ export class StockMovementItem extends Doc {
|
|||||||
this.parentdoc?.movementType === 'MaterialTransfer',
|
this.parentdoc?.movementType === 'MaterialTransfer',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
readOnly: ReadOnlyMap = {
|
||||||
|
fromLocation: () =>
|
||||||
|
this.parentdoc?.movementType === MovementType.MaterialReceipt,
|
||||||
|
toLocation: () =>
|
||||||
|
this.parentdoc?.movementType === MovementType.MaterialIssue,
|
||||||
|
};
|
||||||
|
|
||||||
static createFilters: FiltersMap = {
|
static createFilters: FiltersMap = {
|
||||||
item: () => ({ trackItem: true, itemType: 'Product' }),
|
item: () => ({ trackItem: true, itemType: 'Product' }),
|
||||||
fromLocation: (doc: Doc) => ({ item: (doc.item ?? '') as string }),
|
fromLocation: (doc: Doc) => ({ item: (doc.item ?? '') as string }),
|
||||||
|
@ -1 +1,12 @@
|
|||||||
|
import { Doc } from "fyo/model/doc";
|
||||||
|
import { FilterFunction } from "fyo/model/types";
|
||||||
|
import { QueryFilter } from "utils/db/types";
|
||||||
|
|
||||||
|
export const locationFilter: FilterFunction = (doc: Doc) => {
|
||||||
|
const item = doc.item;
|
||||||
|
if (!doc.item) {
|
||||||
|
return { item: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { item: ['in', [null, item]] } as QueryFilter;
|
||||||
|
};
|
||||||
|
@ -149,7 +149,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
console.log(routes);
|
|
||||||
|
|
||||||
export function getEntryRoute(schemaName: string, name: string) {
|
export function getEntryRoute(schemaName: string, name: string) {
|
||||||
if (
|
if (
|
||||||
|
@ -3,6 +3,7 @@ import { ConfigFile, ConfigKeys } from 'fyo/core/types';
|
|||||||
import { getRegionalModels, models } from 'models/index';
|
import { getRegionalModels, models } from 'models/index';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { getRandomString, getValueMapFromList } from 'utils/index';
|
import { getRandomString, getValueMapFromList } from 'utils/index';
|
||||||
|
import { getIsInventoryEnabled } from './misc';
|
||||||
|
|
||||||
export async function initializeInstance(
|
export async function initializeInstance(
|
||||||
dbPath: string,
|
dbPath: string,
|
||||||
@ -27,6 +28,11 @@ export async function initializeInstance(
|
|||||||
await setInstanceId(fyo);
|
await setInstanceId(fyo);
|
||||||
await setOpenCount(fyo);
|
await setOpenCount(fyo);
|
||||||
await setCurrencySymbols(fyo);
|
await setCurrencySymbols(fyo);
|
||||||
|
await setAppFlags(fyo);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setAppFlags(fyo: Fyo) {
|
||||||
|
fyo.store.appFlags.isInventoryEnabled = await getIsInventoryEnabled(fyo);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeDbIfConnected(fyo: Fyo) {
|
async function closeDbIfConnected(fyo: Fyo) {
|
||||||
|
@ -129,3 +129,12 @@ export async function convertFileToDataURL(file: File, type: string) {
|
|||||||
const array = new Uint8Array(buffer);
|
const array = new Uint8Array(buffer);
|
||||||
return await getDataURL(type, array);
|
return await getDataURL(type, array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getIsInventoryEnabled(fyo: Fyo) {
|
||||||
|
const values = await fyo.db.getAllRaw('Item', {
|
||||||
|
fields: ['name'],
|
||||||
|
filters: { trackItem: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!values.length;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Fyo, t } from 'fyo';
|
import { t } from 'fyo';
|
||||||
import { fyo } from '../initFyo';
|
import { fyo } from '../initFyo';
|
||||||
|
import { getIsInventoryEnabled } from './misc';
|
||||||
import { SidebarConfig, SidebarRoot } from './types';
|
import { SidebarConfig, SidebarRoot } from './types';
|
||||||
|
|
||||||
export async function getSidebarConfig(): Promise<SidebarConfig> {
|
export async function getSidebarConfig(): Promise<SidebarConfig> {
|
||||||
@ -7,15 +8,6 @@ export async function getSidebarConfig(): Promise<SidebarConfig> {
|
|||||||
return getFilteredSidebar(sideBar);
|
return getFilteredSidebar(sideBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getIsInventoryEnabled(fyo: Fyo) {
|
|
||||||
const values = await fyo.db.getAllRaw('Item', {
|
|
||||||
fields: ['name'],
|
|
||||||
filters: { trackItem: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
return !!values.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilteredSidebar(sideBar: SidebarConfig): SidebarConfig {
|
function getFilteredSidebar(sideBar: SidebarConfig): SidebarConfig {
|
||||||
return sideBar.filter((root) => {
|
return sideBar.filter((root) => {
|
||||||
root.items = root.items?.filter((item) => {
|
root.items = root.items?.filter((item) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user