2022-04-19 05:59:36 +00:00
|
|
|
import { Fyo } from 'fyo';
|
2022-04-24 06:48:44 +00:00
|
|
|
import { Doc } from 'fyo/model/doc';
|
2022-04-23 09:23:44 +00:00
|
|
|
import { isPesa } from 'fyo/utils';
|
|
|
|
import { ValueError } from 'fyo/utils/errors';
|
|
|
|
import { DateTime } from 'luxon';
|
2022-04-25 06:33:31 +00:00
|
|
|
import { Field, FieldTypeEnum, RawValue, TargetField } from 'schemas/types';
|
2022-11-07 07:15:38 +00:00
|
|
|
import { getIsNullOrUndef, safeParseFloat, safeParseInt } from 'utils';
|
2022-04-18 05:07:36 +00:00
|
|
|
import { DatabaseHandler } from './dbHandler';
|
2022-10-13 11:25:34 +00:00
|
|
|
import { Attachment, DocValue, DocValueMap, RawValueMap } from './types';
|
2022-04-18 05:07:36 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* # Converter
|
|
|
|
*
|
|
|
|
* Basically converts serializable RawValues from the db to DocValues used
|
|
|
|
* by the frontend and vice versa.
|
|
|
|
*
|
|
|
|
* ## Value Conversion
|
|
|
|
* It exposes two static methods: `toRawValue` and `toDocValue` that can be
|
|
|
|
* used elsewhere given the fieldtype.
|
|
|
|
*
|
|
|
|
* ## Map Conversion
|
|
|
|
* Two methods `toDocValueMap` and `toRawValueMap` are exposed but should be
|
|
|
|
* used only from the `dbHandler`.
|
|
|
|
*/
|
|
|
|
|
|
|
|
export class Converter {
|
|
|
|
db: DatabaseHandler;
|
2022-04-19 05:59:36 +00:00
|
|
|
fyo: Fyo;
|
2022-04-18 05:07:36 +00:00
|
|
|
|
2022-04-19 05:59:36 +00:00
|
|
|
constructor(db: DatabaseHandler, fyo: Fyo) {
|
2022-04-18 05:07:36 +00:00
|
|
|
this.db = db;
|
2022-04-19 05:59:36 +00:00
|
|
|
this.fyo = fyo;
|
2022-04-18 05:07:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toDocValueMap(
|
|
|
|
schemaName: string,
|
|
|
|
rawValueMap: RawValueMap | RawValueMap[]
|
|
|
|
): DocValueMap | DocValueMap[] {
|
2022-08-24 10:36:48 +00:00
|
|
|
rawValueMap ??= {};
|
2022-04-18 05:07:36 +00:00
|
|
|
if (Array.isArray(rawValueMap)) {
|
|
|
|
return rawValueMap.map((dv) => this.#toDocValueMap(schemaName, dv));
|
|
|
|
} else {
|
|
|
|
return this.#toDocValueMap(schemaName, rawValueMap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
toRawValueMap(
|
|
|
|
schemaName: string,
|
|
|
|
docValueMap: DocValueMap | DocValueMap[]
|
|
|
|
): RawValueMap | RawValueMap[] {
|
2022-08-24 10:36:48 +00:00
|
|
|
docValueMap ??= {};
|
2022-04-18 05:07:36 +00:00
|
|
|
if (Array.isArray(docValueMap)) {
|
|
|
|
return docValueMap.map((dv) => this.#toRawValueMap(schemaName, dv));
|
|
|
|
} else {
|
|
|
|
return this.#toRawValueMap(schemaName, docValueMap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
static toDocValue(value: RawValue, field: Field, fyo: Fyo): DocValue {
|
|
|
|
switch (field.fieldtype) {
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Currency:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toDocCurrency(value, field, fyo);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Date:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toDocDate(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Datetime:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toDocDate(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Int:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toDocInt(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Float:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toDocFloat(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Check:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toDocCheck(value, field);
|
2022-10-13 11:25:34 +00:00
|
|
|
case FieldTypeEnum.Attachment:
|
|
|
|
return toDocAttachment(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
default:
|
2022-04-25 06:33:31 +00:00
|
|
|
return toDocString(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
static toRawValue(value: DocValue, field: Field, fyo: Fyo): RawValue {
|
|
|
|
switch (field.fieldtype) {
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Currency:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toRawCurrency(value, fyo, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Date:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toRawDate(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Datetime:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toRawDateTime(value, field);
|
|
|
|
case FieldTypeEnum.Int:
|
|
|
|
return toRawInt(value, field);
|
|
|
|
case FieldTypeEnum.Float:
|
|
|
|
return toRawFloat(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
case FieldTypeEnum.Check:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toRawCheck(value, field);
|
2022-04-28 12:37:07 +00:00
|
|
|
case FieldTypeEnum.Link:
|
|
|
|
return toRawLink(value, field);
|
2022-10-13 11:25:34 +00:00
|
|
|
case FieldTypeEnum.Attachment:
|
|
|
|
return toRawAttachment(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
default:
|
2022-04-23 09:23:44 +00:00
|
|
|
return toRawString(value, field);
|
2022-04-18 05:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#toDocValueMap(schemaName: string, rawValueMap: RawValueMap): DocValueMap {
|
2022-11-14 08:30:11 +00:00
|
|
|
const fieldValueMap = this.db.fieldMap[schemaName];
|
2022-04-18 05:07:36 +00:00
|
|
|
const docValueMap: DocValueMap = {};
|
|
|
|
|
|
|
|
for (const fieldname in rawValueMap) {
|
|
|
|
const field = fieldValueMap[fieldname];
|
|
|
|
const rawValue = rawValueMap[fieldname];
|
2022-05-17 06:09:08 +00:00
|
|
|
if (!field) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-04-18 05:07:36 +00:00
|
|
|
|
|
|
|
if (Array.isArray(rawValue)) {
|
2022-04-25 06:33:31 +00:00
|
|
|
const parentSchemaName = (field as TargetField).target;
|
2022-04-18 05:07:36 +00:00
|
|
|
docValueMap[fieldname] = rawValue.map((rv) =>
|
2022-04-25 06:33:31 +00:00
|
|
|
this.#toDocValueMap(parentSchemaName, rv)
|
2022-04-18 05:07:36 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
docValueMap[fieldname] = Converter.toDocValue(
|
|
|
|
rawValue,
|
2022-04-23 09:23:44 +00:00
|
|
|
field,
|
2022-04-19 05:59:36 +00:00
|
|
|
this.fyo
|
2022-04-18 05:07:36 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return docValueMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
#toRawValueMap(schemaName: string, docValueMap: DocValueMap): RawValueMap {
|
2022-11-14 08:30:11 +00:00
|
|
|
const fieldValueMap = this.db.fieldMap[schemaName];
|
2022-04-18 05:07:36 +00:00
|
|
|
const rawValueMap: RawValueMap = {};
|
|
|
|
|
|
|
|
for (const fieldname in docValueMap) {
|
|
|
|
const field = fieldValueMap[fieldname];
|
|
|
|
const docValue = docValueMap[fieldname];
|
|
|
|
|
|
|
|
if (Array.isArray(docValue)) {
|
2022-04-25 06:33:31 +00:00
|
|
|
const parentSchemaName = (field as TargetField).target;
|
|
|
|
|
2022-04-18 05:07:36 +00:00
|
|
|
rawValueMap[fieldname] = docValue.map((value) => {
|
|
|
|
if (value instanceof Doc) {
|
2022-04-25 06:33:31 +00:00
|
|
|
return this.#toRawValueMap(parentSchemaName, value.getValidDict());
|
2022-04-18 05:07:36 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 08:52:54 +00:00
|
|
|
return this.#toRawValueMap(parentSchemaName, value);
|
2022-04-18 05:07:36 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
rawValueMap[fieldname] = Converter.toRawValue(
|
|
|
|
docValue,
|
2022-04-23 09:23:44 +00:00
|
|
|
field,
|
|
|
|
this.fyo
|
2022-04-18 05:07:36 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rawValueMap;
|
|
|
|
}
|
|
|
|
}
|
2022-04-23 09:23:44 +00:00
|
|
|
|
2022-04-25 06:33:31 +00:00
|
|
|
function toDocString(value: RawValue, field: Field) {
|
2022-05-02 15:17:14 +00:00
|
|
|
if (value === null) {
|
|
|
|
return null;
|
2022-04-25 06:33:31 +00:00
|
|
|
}
|
|
|
|
|
2022-05-11 10:44:31 +00:00
|
|
|
if (value === undefined) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-05-02 15:17:14 +00:00
|
|
|
if (typeof value === 'string') {
|
2022-04-25 06:33:31 +00:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
function toDocDate(value: RawValue, field: Field) {
|
2023-06-22 08:52:54 +00:00
|
|
|
if ((value as unknown) instanceof Date) {
|
2022-05-10 09:26:17 +00:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2022-05-05 10:44:26 +00:00
|
|
|
if (value === null || value === '') {
|
2022-05-02 15:17:14 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
if (typeof value !== 'number' && typeof value !== 'string') {
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
|
|
|
const date = new Date(value);
|
|
|
|
if (date.toString() === 'Invalid Date') {
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toDocCurrency(value: RawValue, field: Field, fyo: Fyo) {
|
2022-05-10 09:26:17 +00:00
|
|
|
if (isPesa(value)) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2022-05-05 10:44:26 +00:00
|
|
|
if (value === '') {
|
|
|
|
return fyo.pesa(0);
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
if (typeof value === 'string') {
|
|
|
|
return fyo.pesa(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
return fyo.pesa(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
return fyo.pesa(Number(value));
|
|
|
|
}
|
|
|
|
|
2022-05-02 15:17:14 +00:00
|
|
|
if (value === null) {
|
|
|
|
return fyo.pesa(0);
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
|
|
|
function toDocInt(value: RawValue, field: Field): number {
|
2022-05-05 10:44:26 +00:00
|
|
|
if (value === '') {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
if (typeof value === 'string') {
|
2022-11-07 07:15:38 +00:00
|
|
|
value = safeParseInt(value);
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return toDocFloat(value, field);
|
|
|
|
}
|
|
|
|
|
|
|
|
function toDocFloat(value: RawValue, field: Field): number {
|
2022-05-05 10:44:26 +00:00
|
|
|
if (value === '') {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
return Number(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
2022-11-07 07:15:38 +00:00
|
|
|
value = safeParseFloat(value);
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 15:17:14 +00:00
|
|
|
if (value === null) {
|
|
|
|
value = 0;
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
if (typeof value === 'number' && !Number.isNaN(value)) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
|
|
|
function toDocCheck(value: RawValue, field: Field): boolean {
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
2022-11-07 07:15:38 +00:00
|
|
|
return !!safeParseFloat(value);
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
return Boolean(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
2022-10-13 11:25:34 +00:00
|
|
|
function toDocAttachment(value: RawValue, field: Field): null | Attachment {
|
|
|
|
if (!value) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2023-06-22 08:52:54 +00:00
|
|
|
return (JSON.parse(value) as Attachment) || null;
|
2022-10-13 11:25:34 +00:00
|
|
|
} catch {
|
|
|
|
throwError(value, field, 'doc');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
function toRawCurrency(value: DocValue, fyo: Fyo, field: Field): string {
|
|
|
|
if (isPesa(value)) {
|
2023-01-16 04:13:59 +00:00
|
|
|
return value.store;
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (getIsNullOrUndef(value)) {
|
|
|
|
return fyo.pesa(0).store;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
return fyo.pesa(value).store;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
return fyo.pesa(value).store;
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
|
|
|
function toRawInt(value: DocValue, field: Field): number {
|
|
|
|
if (typeof value === 'string') {
|
2022-11-07 07:15:38 +00:00
|
|
|
return safeParseInt(value);
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (getIsNullOrUndef(value)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
2023-06-22 08:52:54 +00:00
|
|
|
return Math.floor(value);
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
|
|
|
function toRawFloat(value: DocValue, field: Field): number {
|
|
|
|
if (typeof value === 'string') {
|
2022-11-07 07:15:38 +00:00
|
|
|
return safeParseFloat(value);
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (getIsNullOrUndef(value)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
2022-04-24 06:48:44 +00:00
|
|
|
function toRawDate(value: DocValue, field: Field): string | null {
|
2022-04-23 09:23:44 +00:00
|
|
|
const dateTime = toRawDateTime(value, field);
|
2022-04-24 06:48:44 +00:00
|
|
|
if (dateTime === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
return dateTime.split('T')[0];
|
|
|
|
}
|
|
|
|
|
2022-04-24 06:48:44 +00:00
|
|
|
function toRawDateTime(value: DocValue, field: Field): string | null {
|
|
|
|
if (value === null) {
|
|
|
|
return null;
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value instanceof Date) {
|
2023-06-22 08:52:54 +00:00
|
|
|
return value.toISOString();
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value instanceof DateTime) {
|
|
|
|
return (value as DateTime).toISO();
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
|
|
|
function toRawCheck(value: DocValue, field: Field): number {
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
value = Boolean(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
return Number(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
2022-04-24 06:48:44 +00:00
|
|
|
function toRawString(value: DocValue, field: Field): string | null {
|
|
|
|
if (value === null) {
|
|
|
|
return null;
|
2022-04-23 09:23:44 +00:00
|
|
|
}
|
|
|
|
|
2022-05-11 10:44:31 +00:00
|
|
|
if (value === undefined) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
if (typeof value === 'string') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
2022-04-28 12:37:07 +00:00
|
|
|
function toRawLink(value: DocValue, field: Field): string | null {
|
|
|
|
if (value === null || !(value as string)?.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
2022-10-13 11:25:34 +00:00
|
|
|
function toRawAttachment(value: DocValue, field: Field): null | string {
|
|
|
|
if (!value) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
(value as Attachment)?.name &&
|
|
|
|
(value as Attachment)?.data &&
|
|
|
|
(value as Attachment)?.type
|
|
|
|
) {
|
|
|
|
return JSON.stringify(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
throwError(value, field, 'raw');
|
|
|
|
}
|
|
|
|
|
2022-04-23 09:23:44 +00:00
|
|
|
function throwError<T>(value: T, field: Field, type: 'raw' | 'doc'): never {
|
|
|
|
throw new ValueError(
|
2023-06-22 08:52:54 +00:00
|
|
|
`invalid ${type} conversion '${String(
|
|
|
|
value
|
|
|
|
)}' of type ${typeof value} found, field: ${JSON.stringify(field)}`
|
2022-04-23 09:23:44 +00:00
|
|
|
);
|
|
|
|
}
|