/** * @utils scripts útiles comunes para todo el proyecto de Unolet. * * @version 3.0 * @author Unolet * @copyright Unolet * @see https://blog.unolet.com * */ const True: boolean = true; const False: boolean = false; const None = null; const PORCENTAJE: string = "PORCENTAJE"; const FIJO: string = "FIJO"; const ENTRADA: string = "ENTRADA"; const SALIDA: string = "SALIDA"; const FACTURA_CONTADO: string = "FACTURA_CONTADO"; const FACTURA_CREDITO: string = "FACTURA_CREDITO"; const DEVOLUCION: string = "DEVOLUCION"; const COTIZACION: string = "COTIZACION"; const COMPRA: string = "COMPRA"; const ORDEN_COMPRA: string = "ORDEN_COMPRA"; const TRANSFERENCIA: string = "TRANSFERENCIA"; const CONTEO_FISICO: string = "CONTEO_FISICO"; const CONTABILIDAD_CREDITO: string = "CONTABILIDAD_CREDITO"; const CONTABILIDAD_DEBITO: string = "CONTABILIDAD_DEBITO"; // Usado por Django (como una función) para el parámetro 'default' en los campos de modelos, // cuando a este campo no se le indica un valor. const NOT_PROVIDED: string = "_NOT_PROVIDED"; const STATIC_URL: string = "/static/"; const MEDIA_URL: string = "/media/"; /** * Comprueba que el valor pasado tenga un valor verdadero. * @param value Valor que se desea evaluar. */ function is(value: any): boolean { if (value == undefined) { return false; } if (isNaN(value)) { return false; } if (value) { return true; } return false; } /** * Comprueba que todos sus valores sean verdaderos. * @param a Primer valor a comparar. * @param b Segundo valor a comparar. */ function and(a: any, b: any): boolean { if (!is(a)) { return false; } if (!is(b)) { return false; } return true; } /** * Devuelve el primer valor que de como resultado positivo. */ function firstOf(a: any, b: any = null): any { if (is(a)) { return a; } return b; } /** * @returns any.toString() */ function str(any: any): string { return any.toString(); } /** * Intenta converir el valor en un número entero. * @param number número o texto a convertir. * @param alt_return si falla la conversión, devolverá este valor en su lugar. * @returns parseInt(number) or alt_return */ function int(number: any, alt_return: number = 0): number { return firstOf(parseInt(number), alt_return); } /** * Intenta converir el valor en un número de coma flotante. * @param number número o texto a convertir. * @param alt_return si falla la conversión, devolverá este valor en su lugar. * @returns parseFloat(number) or alt_return */ function float(number: any, alt_return: number = 0): number { return firstOf(parseFloat(number), alt_return); } /** * Calcula el impuesto del importe indicado, según los valores pasados. * @param importe valor numérico al cual se calculará el impuesto. * @param impuesto_type tipo de impuesto (PORCENTAJE or FIJO). * @param impuesto_value valor del impuesto. * @returns el impuesto resultante. */ function calcularImpuesto(importe: number, impuesto_type: string = PORCENTAJE, impuesto_value: number = 0): number { if (impuesto_type == PORCENTAJE) { return (importe * impuesto_value) / 100; } if (impuesto_type == FIJO) { return impuesto_value; } return 0; } /** * Extrae el impuesto del importe indicado, según los valores pasados. * @param importe valor numérico al cual se calculará el impuesto. * @param impuesto_type tipo de impuesto (PORCENTAJE or FIJO). * @param impuesto_value valor del impuesto. * @returns el impuesto resultante. */ function extraerImpuesto(importe: number = 0, impuesto_type: string = PORCENTAJE, impuesto_value: number = 0): number { if (impuesto_type == PORCENTAJE) { return importe / ((impuesto_value / 100) + 1) } if (impuesto_type == FIJO) { return importe - impuesto_value; } return importe; } /** * Formatea un valor numérico al mismo valor divido por coma (,) de miles. * @param num número o texto que representa el número que queremos formatear. * @param decimal_places cantidad de decimales que tendrá el número resultante. * @returns un string con el número indicado con división de miles. */ function intcomma(num: any, decimal_places: number = 2): string { try { num = parseFloat(num); } catch (error) { return ""; } if (isNaN(num)) { return ""; } let num_parts = num.toString().split("."); num_parts[0] = num_parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); if (num_parts[1] != undefined) { try { num_parts[1] = num_parts[1].slice(0, decimal_places); } catch (error) { console.warn("No se agregaron los decimales intcomma(" + num + ", " + decimal_places + ")"); console.warn(error); } } return num_parts.join("."); } /** * Redirecciona hacia la URL resultante de reverse(name). * @param urlname nombre de la url (urlpath). */ function redirectFromURLName(urlname: string) { window.location.href = "/redirectfromurlname/?name=" + urlname; } /** * Realiza una consulta request GET o POST de forma síncronizada o asíncrona. * @param url URL. * @param method "GET" or "POST" * @param isasync Si es true, la consulta será asíncrona. * @returns Contenido de la respuesta en un string. */ function request(url: string, method: string = "GET", isasync: boolean = false): string { let request = new XMLHttpRequest(); request.open(method, url, isasync); request.send(null); if (request.status === 200) { return request.responseText; } else { console.error("Error en request.", request.status); return request.responseText; } } /** * Obtiene los campos del modelo indicado. * @param app_label Nombre de la aplicación. * @param model_name Nombre del modelo. * @param include_parents Si es True, incluirá los campos padres de forma recursiva. * @param include_hidden Si es True, inclirá los campos ocultos. * @param for_list Si es true, obtendrá solo los campos ideales para mostrarse en un listado. De indicarse en true, * no será necesario los parámetros include_parents e include_hidden. * @returns JSON.parse(request.responseText). */ function getModelFields(app_label: string, model_name: string, include_parents: boolean = true, include_hidden: boolean = false, for_list: boolean = false): JSON { let url = `/unolet/api/modelfields/?app_label=${app_label}&model_name=${model_name}&include_parents=${include_parents}&include_hidden=${include_hidden}&for_list=${for_list}`; let response = request(url, "GET", false); return JSON.parse(response); } /** * Contruye los argumentos que se pasan por URL, con los datos del objeto pasado. * @param obj objeto con keys y values. * @returns "key1=val1&key2=val2&key3=val3..." */ function buildUrlArgumentsFromObject(obj: object): string { let list = []; Object.keys(obj).forEach(key => { list.push(`${key}=${obj[key]}`); }); return list.join("&"); } /** * Contruye los argumentos que se pasan por URL, con los datos del array pasado (como valores) y el parámetro name * (como nombre de cada parámetro.). El resultado será un listado de valores para un mismo parámetro que se obtendrá en * el servidor como request.getlist(name). * @param obj objeto con keys y values. * @returns "name=val1&name=val2&name=val3..." */ function buildUrlArgumentsFromList(name: string, list): string { let list2 = []; list.forEach(value => { list2.push(`${name}=${value}`); }); return list2.join("&"); } /** * Obtiene la URL correspondiente para el nombre de URL indicado realizado un reverse(name, kwargs={...}) en el servidor. * @param name nombre de la URL. * @param kwargs Argumentos a pasar por URL (opcional para algunas). * @returns la URL resultante. */ function reverse(name: string, kwargs: object = {}): string { let base_url = "/unolet/api/urlfromname/"; base_url += "?name=" + name + "&" + buildUrlArgumentsFromObject(kwargs); let data = JSON.parse(request(base_url)); if (data.error) { console.error(data.message); } else { return data.data.url; } } class DjangoField { name: string; verbose_name: string; help_text: string; max_length: number; blank: boolean; _null: boolean; editable: boolean; choices: string[]; type: string; _default: any; related_model: string; related_app: string; constructor(name: string, verbose_name: string = null, help_text: string = "", max_length: number = null, blank: boolean = false, _null: boolean = false, editable: boolean = true, choices: string[] = null, type: string = "CharField", _default: any = NOT_PROVIDED, related_model: string, related_app: string) { this.name = name; this.verbose_name = verbose_name || this.name; this.help_text = help_text; this.max_length = max_length; this.blank = blank; this._null = _null; this.editable = editable; this.choices = choices; this.type = type; this._default = _default; this.related_model = related_model; this.related_app = related_app; } getJsType() { if (this.type in ["CharField", "TextField"]) { return String; } else if (this.type in ["DecimalField", "IntegerField", "FloatField"]) { return Number; } else if (this.type in ["BooleanField"]) { return Boolean; } else { return Object; } } getCssClassesForTable(field: DjangoField) { let type: string = field ? field.type: this.type; let cssclasses: string[] = []; if (type in ["DecimalField", "IntegerField", "FloatField"]) { cssclasses.push("text-end", "") } return cssclasses.join(" "); } parse(value: any) { let jstype: any = this.getJsType(); return jstype(value); } parseDisplay(value: any) { let jstype: any = this.getJsType(); value = jstype(value); if (jstype === Number) { return intcomma(value); } else { return value; } } } function getDjangoFieldFromObject(obj) { return new DjangoField(obj.name, obj.verbose_name, obj.help_text, obj.max_length, obj.blank, obj.null, obj.editable, obj.choices, obj.type, obj.default, obj.related_model, obj.related_app); }