2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

Merge pull request #557 from frappe/feat-uom-and-conversion-factor

Feat uom and conversion factor
This commit is contained in:
Alan 2023-02-21 02:47:02 -08:00 committed by GitHub
commit d6a4428d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 460 additions and 45 deletions

View File

@ -1,4 +1,4 @@
import { Fyo } from 'fyo';
import { Fyo, t } from 'fyo';
import { DocValue, DocValueMap } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
@ -13,6 +13,7 @@ import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { FieldTypeEnum, Schema } from 'schemas/types';
import { safeParseFloat } from 'utils/index';
import { Invoice } from '../Invoice/Invoice';
import { Item } from '../Item/Item';
@ -22,7 +23,13 @@ export abstract class InvoiceItem extends Doc {
amount?: Money;
parentdoc?: Invoice;
rate?: Money;
unit?: string;
transferUnit?: string;
quantity?: number;
transferQuantity?: number;
unitConversionFactor?: number;
tax?: string;
stockNotTransferred?: number;
@ -44,6 +51,10 @@ export abstract class InvoiceItem extends Doc {
return !!this.fyo.singles?.AccountingSettings?.enableDiscounting;
}
get enableInventory() {
return !!this.fyo.singles?.AccountingSettings?.enableInventory;
}
get currency() {
return this.parentdoc?.currency ?? DEFAULT_CURRENCY;
}
@ -136,8 +147,41 @@ export abstract class InvoiceItem extends Doc {
'setItemDiscountAmount',
],
},
unit: {
formula: async () =>
(await this.fyo.getValue(
'Item',
this.item as string,
'unit'
)) as string,
dependsOn: ['item'],
},
transferUnit: {
formula: async (fieldname) => {
if (fieldname === 'quantity' || fieldname === 'unit') {
return this.unit;
}
return (await this.fyo.getValue(
'Item',
this.item as string,
'unit'
)) as string;
},
dependsOn: ['item', 'unit'],
},
transferQuantity: {
formula: async (fieldname) => {
if (fieldname === 'quantity' || this.unit === this.transferUnit) {
return this.quantity;
}
return this.transferQuantity;
},
dependsOn: ['item', 'quantity'],
},
quantity: {
formula: async () => {
formula: async (fieldname) => {
if (!this.item) {
return this.quantity as number;
}
@ -146,15 +190,43 @@ export abstract class InvoiceItem extends Doc {
ModelNameEnum.Item,
this.item as string
);
const unitDoc = itemDoc.getLink('uom');
const unitDoc = itemDoc.getLink('unit');
if (unitDoc?.isWhole) {
return Math.round(this.quantity as number);
let quantity: number = this.quantity ?? 1;
if (fieldname === 'transferQuantity') {
quantity = this.transferQuantity! * this.unitConversionFactor!;
}
return this.quantity as number;
if (unitDoc?.isWhole) {
return Math.round(quantity);
}
return safeParseFloat(quantity);
},
dependsOn: ['quantity'],
dependsOn: [
'quantity',
'transferQuantity',
'transferUnit',
'unitConversionFactor',
],
},
unitConversionFactor: {
formula: async () => {
if (this.unit === this.transferUnit) {
return 1;
}
const conversionFactor = await this.fyo.db.getAll(
ModelNameEnum.UOMConversionItem,
{
fields: ['conversionFactor'],
filters: { parent: this.item! },
}
);
return safeParseFloat(conversionFactor[0]?.conversionFactor ?? 1);
},
dependsOn: ['transferUnit'],
},
account: {
formula: () => {
@ -319,6 +391,22 @@ export abstract class InvoiceItem extends Doc {
}) cannot be greater than 100.`
);
},
transferUnit: async (value: DocValue) => {
if (!this.item) {
return;
}
const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, {
fields: ['parent'],
filters: { uom: value as string, parent: this.item! },
});
if (item.length < 1)
throw new ValidationError(
t`Transfer Unit ${value as string} is not applicable for Item ${this
.item!}`
);
},
};
hidden: HiddenMap = {
@ -342,6 +430,9 @@ export abstract class InvoiceItem extends Doc {
!(this.enableDiscounting && !!this.setItemDiscountAmount),
itemDiscountPercent: () =>
!(this.enableDiscounting && !this.setItemDiscountAmount),
transferUnit: () => !this.enableInventory,
transferQuantity: () => !this.enableInventory,
unitConversionFactor: () => !this.enableInventory,
};
static filters: FiltersMap = {

View File

@ -122,6 +122,7 @@ export class Item extends Doc {
this.itemType !== 'Product' ||
(this.inserted && !this.trackItem),
barcode: () => !this.fyo.singles.InventorySettings?.enableBarcodes,
uomConversions: () => !this.fyo.singles.AccountingSettings?.enableInventory,
};
readOnly: ReadOnlyMap = {

View File

@ -1,3 +1,5 @@
import { t } from 'fyo';
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
FiltersMap,
@ -9,6 +11,7 @@ import {
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { safeParseFloat } from 'utils/index';
import { StockMovement } from './StockMovement';
import { MovementType } from './types';
@ -17,7 +20,13 @@ export class StockMovementItem extends Doc {
item?: string;
fromLocation?: string;
toLocation?: string;
unit?: string;
transferUnit?: string;
quantity?: number;
transferQuantity?: number;
unitConversionFactor?: number;
rate?: Money;
amount?: Money;
parentdoc?: StockMovement;
@ -89,6 +98,87 @@ export class StockMovementItem extends Doc {
},
dependsOn: ['movementType'],
},
unit: {
formula: async () =>
(await this.fyo.getValue(
'Item',
this.item as string,
'unit'
)) as string,
dependsOn: ['item'],
},
transferUnit: {
formula: async (fieldname) => {
if (fieldname === 'quantity' || fieldname === 'unit') {
return this.unit;
}
return (await this.fyo.getValue(
'Item',
this.item as string,
'unit'
)) as string;
},
dependsOn: ['item', 'unit'],
},
transferQuantity: {
formula: async (fieldname) => {
if (fieldname === 'quantity' || this.unit === this.transferUnit) {
return this.quantity;
}
return this.transferQuantity;
},
dependsOn: ['item', 'quantity'],
},
quantity: {
formula: async (fieldname) => {
if (!this.item) {
return this.quantity as number;
}
const itemDoc = await this.fyo.doc.getDoc(
ModelNameEnum.Item,
this.item as string
);
const unitDoc = itemDoc.getLink('uom');
let quantity: number = this.quantity ?? 1;
if (fieldname === 'transferQuantity') {
quantity = this.transferQuantity! * this.unitConversionFactor!;
}
if (unitDoc?.isWhole) {
return Math.round(quantity);
}
return safeParseFloat(quantity);
},
dependsOn: [
'quantity',
'transferQuantity',
'transferUnit',
'unitConversionFactor',
],
},
unitConversionFactor: {
formula: async () => {
if (this.unit === this.transferUnit) {
return 1;
}
const conversionFactor = await this.fyo.db.getAll(
ModelNameEnum.UOMConversionItem,
{
fields: ['conversionFactor'],
filters: { parent: this.item! },
}
);
return safeParseFloat(conversionFactor[0]?.conversionFactor ?? 1);
},
dependsOn: ['transferUnit'],
},
};
validations: ValidationMap = {
@ -114,6 +204,22 @@ export class StockMovementItem extends Doc {
);
}
},
transferUnit: async (value: DocValue) => {
if (!this.item) {
return;
}
const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, {
fields: ['parent'],
filters: { uom: value as string, parent: this.item! },
});
if (item.length < 1)
throw new ValidationError(
t`Transfer Unit ${value as string} is not applicable for Item ${this
.item!}`
);
},
};
required: RequiredMap = {

View File

@ -1,15 +1,24 @@
import { t } from 'fyo';
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { FiltersMap, FormulaMap } from 'fyo/model/types';
import { FiltersMap, FormulaMap, ValidationMap } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { safeParseFloat } from 'utils/index';
export class StockTransferItem extends Doc {
item?: string;
location?: string;
unit?: string;
transferUnit?: string;
quantity?: number;
transferQuantity?: number;
unitConversionFactor?: number;
rate?: Money;
amount?: Money;
unit?: string;
description?: string;
hsnCode?: number;
@ -32,6 +41,78 @@ export class StockTransferItem extends Doc {
)) as string,
dependsOn: ['item'],
},
transferUnit: {
formula: async (fieldname) => {
if (fieldname === 'quantity' || fieldname === 'unit') {
return this.unit;
}
return (await this.fyo.getValue(
'Item',
this.item as string,
'unit'
)) as string;
},
dependsOn: ['item', 'unit'],
},
transferQuantity: {
formula: async (fieldname) => {
if (fieldname === 'quantity' || this.unit === this.transferUnit) {
return this.quantity;
}
return this.transferQuantity;
},
dependsOn: ['item', 'quantity'],
},
quantity: {
formula: async (fieldname) => {
if (!this.item) {
return this.quantity as number;
}
const itemDoc = await this.fyo.doc.getDoc(
ModelNameEnum.Item,
this.item as string
);
const unitDoc = itemDoc.getLink('uom');
let quantity: number = this.quantity ?? 1;
if (fieldname === 'transferQuantity') {
quantity = this.transferQuantity! * this.unitConversionFactor!;
}
if (unitDoc?.isWhole) {
return Math.round(quantity);
}
return safeParseFloat(quantity);
},
dependsOn: [
'quantity',
'transferQuantity',
'transferUnit',
'unitConversionFactor',
],
},
unitConversionFactor: {
formula: async () => {
if (this.unit === this.transferUnit) {
return 1;
}
const conversionFactor = await this.fyo.db.getAll(
ModelNameEnum.UOMConversionItem,
{
fields: ['conversionFactor'],
filters: { parent: this.item! },
}
);
return safeParseFloat(conversionFactor[0]?.conversionFactor ?? 1);
},
dependsOn: ['transferUnit'],
},
hsnCode: {
formula: async () =>
(await this.fyo.getValue(
@ -63,26 +144,6 @@ export class StockTransferItem extends Doc {
},
dependsOn: ['item'],
},
quantity: {
formula: async () => {
if (!this.item) {
return this.quantity as number;
}
const itemDoc = await this.fyo.doc.getDoc(
ModelNameEnum.Item,
this.item as string
);
const unitDoc = itemDoc.getLink('unit');
if (unitDoc?.isWhole) {
return Math.round(this.quantity as number);
}
return this.quantity as number;
},
dependsOn: ['quantity'],
},
account: {
formula: () => {
let accountType = 'expenseAccount';
@ -109,6 +170,25 @@ export class StockTransferItem extends Doc {
},
};
validations: ValidationMap = {
transferUnit: async (value: DocValue) => {
if (!this.item) {
return;
}
const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, {
fields: ['parent'],
filters: { uom: value as string, parent: this.item! },
});
if (item.length < 1)
throw new ValidationError(
t`Transfer Unit ${value as string} is not applicable for Item ${this
.item!}`
);
},
};
static filters: FiltersMap = {
item: (doc: Doc) => {
let itemNotFor = 'Sales';

View File

@ -11,6 +11,7 @@ export enum ModelNameEnum {
Defaults = 'Defaults',
Item = 'Item',
UOM = 'UOM',
UOMConversionItem = 'UOMConversionItem',
JournalEntry = 'JournalEntry',
JournalEntryAccount = 'JournalEntryAccount',
Misc = 'Misc',

View File

@ -17,6 +17,36 @@
"label": "Description",
"fieldtype": "Text"
},
{
"fieldname": "rate",
"label": "Rate",
"fieldtype": "Currency",
"required": true
},
{
"fieldname": "transferUnit",
"label": "Transfer Unit",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit"
},
{
"fieldname": "transferQuantity",
"label": "Qty. in Transfer Unit",
"fieldtype": "Float",
"default": 1,
"required": true
},
{
"fieldname": "unit",
"label": "Stock Unit",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit",
"readOnly": true
},
{
"fieldname": "quantity",
"label": "Quantity",
@ -25,10 +55,11 @@
"default": 1
},
{
"fieldname": "rate",
"label": "Rate",
"fieldtype": "Currency",
"required": true
"fieldname": "unitConversionFactor",
"label": "Conversion Factor",
"fieldtype": "Float",
"required": true,
"default": 1
},
{
"fieldname": "account",
@ -103,8 +134,14 @@
"description",
"hsnCode",
"tax",
"quantity",
"rate",
"transferQuantity",
"transferUnit",
"quantity",
"unit",
"unitConversionFactor",
"amount",
"setItemDiscountAmount",
"itemDiscountAmount",

View File

@ -128,7 +128,14 @@
"fieldname": "trackItem",
"label": "Track Item",
"fieldtype": "Check",
"default": false,
"section": "Inventory",
"default": false
},
{
"fieldname": "uomConversions",
"label": "UOM Conversions",
"fieldtype": "Table",
"target": "UOMConversionItem",
"section": "Inventory"
}
],
@ -143,7 +150,8 @@
"expenseAccount",
"barcode",
"hsnCode",
"trackItem"
"trackItem",
"uom"
],
"keywordFields": ["name", "itemType", "for"]
}

View File

@ -62,6 +62,7 @@
"fieldtype": "Table",
"target": "StockMovementItem",
"required": true,
"edit": true,
"section": "Items"
},
{

View File

@ -26,6 +26,30 @@
"target": "Location",
"create": true
},
{
"fieldname": "transferUnit",
"label": "Transfer Unit",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit"
},
{
"fieldname": "transferQuantity",
"label": "Qty. in Transfer Unit",
"fieldtype": "Float",
"default": 1,
"required": true
},
{
"fieldname": "unit",
"label": "Stock Unit",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit",
"readOnly": true
},
{
"fieldname": "quantity",
"label": "Quantity",
@ -33,6 +57,13 @@
"required": true,
"default": 1
},
{
"fieldname": "unitConversionFactor",
"label": "Conversion Factor",
"fieldtype": "Float",
"required": true,
"default": 1
},
{
"fieldname": "rate",
"label": "Rate",
@ -52,7 +83,13 @@
"item",
"fromLocation",
"toLocation",
"transferQuantity",
"transferUnit",
"quantity",
"unit",
"unitConversionFactor",
"rate",
"amount"
]

View File

@ -17,6 +17,30 @@
"target": "Location",
"required": true
},
{
"fieldname": "transferUnit",
"label": "Transfer Unit",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit"
},
{
"fieldname": "transferQuantity",
"label": "Qty. in Transfer Unit",
"fieldtype": "Float",
"default": 1,
"required": true
},
{
"fieldname": "unit",
"label": "Stock Unit",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit",
"readOnly": true
},
{
"fieldname": "quantity",
"label": "Quantity",
@ -24,6 +48,13 @@
"required": true,
"default": 1
},
{
"fieldname": "unitConversionFactor",
"label": "Conversion Factor",
"fieldtype": "Float",
"required": true,
"default": 1
},
{
"fieldname": "rate",
"label": "Rate",
@ -36,14 +67,6 @@
"fieldtype": "Currency",
"readOnly": true
},
{
"fieldname": "unit",
"label": "Unit Type",
"fieldtype": "Link",
"target": "UOM",
"default": "Unit",
"placeholder": "Unit Type"
},
{
"fieldname": "description",
"label": "Description",
@ -60,11 +83,16 @@
"tableFields": ["item", "location", "quantity", "rate", "amount"],
"quickEditFields": [
"item",
"transferQuantity",
"transferUnit",
"quantity",
"unit",
"unitConversionFactor",
"description",
"hsnCode",
"location",
"quantity",
"rate",
"amount"
]

View File

@ -0,0 +1,23 @@
{
"name": "UOMConversionItem",
"label": "UOM Conversion Item",
"isChild": true,
"fields": [
{
"fieldname": "uom",
"label": "UOM",
"fieldtype": "Link",
"target": "UOM",
"create": true,
"required": true
},
{
"fieldname": "conversionFactor",
"label": "Conversion Factor",
"fieldtype": "Float",
"default": 1,
"required": true
}
],
"tableFields": ["uom", "conversionFactor"]
}

View File

@ -37,6 +37,7 @@ import Tax from './app/Tax.json';
import TaxDetail from './app/TaxDetail.json';
import TaxSummary from './app/TaxSummary.json';
import UOM from './app/UOM.json';
import UOMConversionItem from './app/inventory/UOMConversionItem.json';
import PatchRun from './core/PatchRun.json';
import SingleValue from './core/SingleValue.json';
import SystemSettings from './core/SystemSettings.json';
@ -81,6 +82,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
Address as Schema,
Item as Schema,
UOM as Schema,
UOMConversionItem as Schema,
Payment as Schema,
PaymentFor as Schema,