mirror of
https://github.com/frappe/books.git
synced 2025-01-03 15:17:30 +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: '',
|
||||
deviceId: '',
|
||||
openCount: -1,
|
||||
appFlags: {} as Record<string, boolean>,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { strictEqual } from 'assert';
|
||||
import { assertThrows } from 'backend/database/tests/helpers';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import test from 'tape';
|
||||
|
||||
@ -30,55 +31,69 @@ const listenerBOnce = (value: number) => {
|
||||
strictEqual(params.b, value, 'listenerBOnce');
|
||||
};
|
||||
|
||||
test('set A One', function (t) {
|
||||
test('set A One', (t) => {
|
||||
t.equal(obs.hasListener(ObsEvent.A), false, 'pre');
|
||||
|
||||
obs.once(ObsEvent.A, listenerAOnce);
|
||||
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, listenerAEvery), false, 'specific every');
|
||||
t.end()
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('set A Two', function (t) {
|
||||
test('set A Two', (t) => {
|
||||
obs.on(ObsEvent.A, listenerAEvery);
|
||||
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, 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');
|
||||
|
||||
obs.once(ObsEvent.B, listenerBOnce);
|
||||
t.equal(obs.hasListener(ObsEvent.A, listenerBOnce), false, 'specific false');
|
||||
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);
|
||||
t.equal(obs.hasListener(ObsEvent.A), true, 'non 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');
|
||||
await obs.trigger(ObsEvent.A, params.aTwo);
|
||||
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');
|
||||
await obs.trigger(ObsEvent.B, params.b);
|
||||
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);
|
||||
t.equal(obs.hasListener(ObsEvent.A, listenerAEvery), false, 'specific pre');
|
||||
|
||||
t.equal(counter, 2, 'incorrect counter');
|
||||
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 { FiltersMap } from 'fyo/model/types';
|
||||
import { FiltersMap, HiddenMap } from 'fyo/model/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
|
||||
export class Defaults extends Doc {
|
||||
@ -42,6 +42,18 @@ export class Defaults extends Doc {
|
||||
|
||||
static filters: 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<
|
||||
|
@ -10,15 +10,13 @@ import {
|
||||
HiddenMap,
|
||||
ListViewSettings,
|
||||
RequiredMap,
|
||||
ValidationMap
|
||||
ValidationMap,
|
||||
} from 'fyo/model/types';
|
||||
import { NotFoundError, ValidationError } from 'fyo/utils/errors';
|
||||
import {
|
||||
getDocStatus,
|
||||
getDocStatusListColumn,
|
||||
getLedgerLinkAction,
|
||||
getNumberSeries,
|
||||
getStatusMap,
|
||||
statusColor
|
||||
} from 'models/helpers';
|
||||
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||
import { Transactional } from 'models/Transactional/Transactional';
|
||||
@ -619,27 +617,7 @@ export class Payment extends Transactional {
|
||||
|
||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||
return {
|
||||
columns: [
|
||||
'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',
|
||||
],
|
||||
columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -267,3 +267,21 @@ export function getNumberSeries(schemaName: string, fyo: Fyo) {
|
||||
const value = defaults?.[numberSeriesKey] 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;
|
||||
}
|
||||
|
||||
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[]) {
|
||||
const detailsList = transferDetails.map((d) => this.#getSMIDetails(d));
|
||||
for (const details of detailsList) {
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
FormulaMap,
|
||||
ListViewSettings
|
||||
} from 'fyo/model/types';
|
||||
import { getDocStatusListColumn } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { StockManager } from './StockManager';
|
||||
@ -40,15 +41,25 @@ export class StockMovement extends Doc {
|
||||
};
|
||||
|
||||
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> {
|
||||
await super.afterSubmit();
|
||||
const transferDetails = this._getTransferDetails();
|
||||
await this._getStockManager().createTransfers(transferDetails);
|
||||
}
|
||||
|
||||
async afterCancel(): Promise<void> {
|
||||
await super.afterCancel();
|
||||
await this._getStockManager().cancelTransfers();
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,15 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import {
|
||||
FilterFunction,
|
||||
FiltersMap,
|
||||
FormulaMap,
|
||||
ReadOnlyMap,
|
||||
RequiredMap
|
||||
} from 'fyo/model/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { locationFilter } from './helpers';
|
||||
import { StockMovement } from './StockMovement';
|
||||
|
||||
const locationFilter: FilterFunction = (doc: Doc) => {
|
||||
const item = doc.item;
|
||||
if (!doc.item) {
|
||||
return { item: null };
|
||||
}
|
||||
|
||||
return { item: ['in', [null, item]] } as QueryFilter;
|
||||
};
|
||||
import { MovementType } from './types';
|
||||
|
||||
export class StockMovementItem extends Doc {
|
||||
name?: string;
|
||||
@ -50,6 +42,20 @@ export class StockMovementItem extends Doc {
|
||||
formula: () => this.rate!.mul(this.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 = {
|
||||
@ -61,6 +67,13 @@ export class StockMovementItem extends Doc {
|
||||
this.parentdoc?.movementType === 'MaterialTransfer',
|
||||
};
|
||||
|
||||
readOnly: ReadOnlyMap = {
|
||||
fromLocation: () =>
|
||||
this.parentdoc?.movementType === MovementType.MaterialReceipt,
|
||||
toLocation: () =>
|
||||
this.parentdoc?.movementType === MovementType.MaterialIssue,
|
||||
};
|
||||
|
||||
static createFilters: FiltersMap = {
|
||||
item: () => ({ trackItem: true, itemType: 'Product' }),
|
||||
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) {
|
||||
if (
|
||||
|
@ -3,6 +3,7 @@ import { ConfigFile, ConfigKeys } from 'fyo/core/types';
|
||||
import { getRegionalModels, models } from 'models/index';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { getRandomString, getValueMapFromList } from 'utils/index';
|
||||
import { getIsInventoryEnabled } from './misc';
|
||||
|
||||
export async function initializeInstance(
|
||||
dbPath: string,
|
||||
@ -27,6 +28,11 @@ export async function initializeInstance(
|
||||
await setInstanceId(fyo);
|
||||
await setOpenCount(fyo);
|
||||
await setCurrencySymbols(fyo);
|
||||
await setAppFlags(fyo);
|
||||
}
|
||||
|
||||
async function setAppFlags(fyo: Fyo) {
|
||||
fyo.store.appFlags.isInventoryEnabled = await getIsInventoryEnabled(fyo);
|
||||
}
|
||||
|
||||
async function closeDbIfConnected(fyo: Fyo) {
|
||||
|
@ -129,3 +129,12 @@ export async function convertFileToDataURL(file: File, type: string) {
|
||||
const array = new Uint8Array(buffer);
|
||||
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 { getIsInventoryEnabled } from './misc';
|
||||
import { SidebarConfig, SidebarRoot } from './types';
|
||||
|
||||
export async function getSidebarConfig(): Promise<SidebarConfig> {
|
||||
@ -7,15 +8,6 @@ export async function getSidebarConfig(): Promise<SidebarConfig> {
|
||||
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 {
|
||||
return sideBar.filter((root) => {
|
||||
root.items = root.items?.filter((item) => {
|
||||
|
Loading…
Reference in New Issue
Block a user