diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py
index 06ba734..e55adf8 100644
--- a/source/app/controllers/main.py
+++ b/source/app/controllers/main.py
@@ -93,3 +93,27 @@ class AppPartners(object):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
+
+
+class AppProducts(object):
+
+ def __init__(self, db):
+ self._db = db
+
+ def on_get(self, req, resp):
+ values = req.params
+ req.context['result'] = self._db.get_products(values)
+ resp.status = falcon.HTTP_200
+
+ def on_post(self, req, resp):
+ values = req.params
+ #~ print ('VALUES', values)
+ req.context['result'] = self._db.product(values)
+ resp.status = falcon.HTTP_200
+
+ def on_delete(self, req, resp):
+ values = req.params
+ if self._db.delete('product', values['id']):
+ resp.status = falcon.HTTP_200
+ else:
+ resp.status = falcon.HTTP_204
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index 33e841e..7fe8bff 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -12,7 +12,7 @@ import uuid
#~ import bcrypt
-from settings import log, template_lookup, COMPANIES
+from settings import log, template_lookup, COMPANIES, DB_SAT
#~ def _get_hash(password):
@@ -86,6 +86,19 @@ def get_rfcs():
return values
+def get_sat_key(table, key):
+ con = sqlite3.connect(DB_SAT)
+ cursor = con.cursor()
+ sql = 'SELECT key, description FROM {} WHERE key=?'.format(table)
+ cursor.execute(sql, (key,))
+ data = cursor.fetchone()
+ cursor.close()
+ con.close()
+ if data is None:
+ return {'ok': False, 'text': 'No se encontró la clave'}
+ return {'ok': True, 'text': data[1]}
+
+
def now():
return datetime.datetime.now()
diff --git a/source/app/main.py b/source/app/main.py
index 75fbf11..3d795e2 100644
--- a/source/app/main.py
+++ b/source/app/main.py
@@ -12,7 +12,7 @@ from middleware import (
)
from models.db import StorageEngine
from controllers.main import (
- AppLogin, AppLogout, AppAdmin, AppMain, AppValues, AppPartners
+ AppLogin, AppLogout, AppAdmin, AppMain, AppValues, AppPartners, AppProducts
)
from settings import DEBUG
@@ -30,6 +30,7 @@ api.add_route('/admin', AppAdmin(db))
api.add_route('/main', AppMain(db))
api.add_route('/values/{table}', AppValues(db))
api.add_route('/partners', AppPartners(db))
+api.add_route('/products', AppProducts(db))
if DEBUG:
diff --git a/source/app/models/db.py b/source/app/models/db.py
index 61a938a..51a7138 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -20,13 +20,30 @@ class StorageEngine(object):
def _get_formapago(self, values):
return main.SATFormaPago.get_activos()
+ def _get_categorias(self, values):
+ return main.Categorias.get_all()
+
+ def _get_newkey(self, values):
+ return main.Productos.next_key()
+
+ def _get_unidades(self, values):
+ return main.SATUnidades.get_activos()
+
+ def _get_taxes(self, values):
+ return main.SATImpuestos.get_activos()
+
+ def _get_satkey(self, values):
+ return main.get_sat_key(values['key'])
+
def delete(self, table, id):
if table == 'partner':
return main.Socios.remove(id)
+ if table == 'product':
+ return main.Productos.remove(id)
return False
def get_partners(self, values):
- return main.Socios.get(values)
+ return main.Socios.get_(values)
def partner(self, values):
id = int(values['id'])
@@ -35,6 +52,16 @@ class StorageEngine(object):
return main.Socios.actualizar(values, id)
return main.Socios.add(values)
+ def get_products(self, values):
+ return main.Productos.get_(values)
+
+ def product(self, values):
+ id = int(values['id'])
+ del values['id']
+ if id:
+ return main.Productos.actualizar(values, id)
+ return main.Productos.add(values)
+
diff --git a/source/app/models/main.py b/source/app/models/main.py
index 6ab5d12..979e4e8 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -179,6 +179,15 @@ class Categorias(BaseModel):
(('categoria', 'padre'), True),
)
+ @classmethod
+ def get_all(cls):
+ rows = (Categorias.select(
+ Categorias.id,
+ Categorias.categoria.alias('value'),
+ Categorias.padre.alias('parent_id'))
+ ).dicts()
+ return tuple(rows)
+
class CondicionesPago(BaseModel):
condicion = TextField(unique=True)
@@ -199,6 +208,17 @@ class SATUnidades(BaseModel):
(('key', 'name'), True),
)
+ @classmethod
+ def get_activos(cls):
+ rows = (SATUnidades
+ .select(
+ SATUnidades.id,
+ SATUnidades.name.alias('value'))
+ .where(SATUnidades.activo==True)
+ .dicts()
+ )
+ return tuple(rows)
+
class SATFormaPago(BaseModel):
key = TextField(unique=True, index=True)
@@ -265,6 +285,11 @@ class SATImpuestos(BaseModel):
(('key', 'factor', 'tipo', 'tasa'), True),
)
+ @classmethod
+ def get_activos(self):
+ rows = SATImpuestos.select().where(SATImpuestos.activo==True).dicts()
+ return tuple(rows)
+
class SATUsoCfdi(BaseModel):
key = TextField(index=True, unique=True)
@@ -343,7 +368,7 @@ class Socios(BaseModel):
return fields
@classmethod
- def get(cls, values):
+ def get_(cls, values):
if values:
id = int(values['id'])
row = Socios.select().where(Socios.id==id).dicts()[0]
@@ -425,6 +450,129 @@ class Productos(BaseModel):
class Meta:
order_by = ('descripcion',)
+ @classmethod
+ def next_key(cls):
+ value = Productos.select(fn.Max(Productos.id)).scalar()
+ if value is None:
+ value = 1
+ else:
+ value += 1
+ return {'value': value}
+
+ @classmethod
+ def get_(cls, values):
+ if values:
+ id = int(values['id'])
+ row = (Productos
+ .select(
+ Productos.id,
+ Productos.es_activo.alias('es_activo_producto'),
+ Productos.categoria,
+ Productos.clave,
+ Productos.clave_sat,
+ Productos.descripcion,
+ Productos.unidad,
+ Productos.valor_unitario,
+ )
+ .where(Productos.id==id).dicts()[0]
+ )
+ obj = Productos.get(Productos.id==id)
+ taxes = [row.id for row in obj.impuestos]
+ return {'row': row, 'taxes': taxes}
+
+ rows = (Productos
+ .select(
+ Productos.id,
+ Productos.clave,
+ Productos.descripcion,
+ SATUnidades.name.alias('unidad'),
+ Productos.valor_unitario)
+ .join(SATUnidades)
+ .dicts()
+ )
+ return {'ok': True, 'rows': tuple(rows)}
+
+ def _clean(self, values):
+ taxes = util.loads(values.pop('taxes'))
+ fields = util.clean(values)
+
+ fields['es_activo'] = fields.pop('es_activo_producto')
+ fields['descripcion'] = util.spaces(fields['descripcion'])
+ fields['unidad'] = int(fields['unidad'])
+ fields['valor_unitario'] = fields['valor_unitario'].replace(
+ '$', '').replace(',', '')
+
+ fb = ('es_activo', 'inventario')
+ for name in fb:
+ fields[name] = bool(fields[name].replace('0', ''))
+
+ return fields, taxes
+
+ @classmethod
+ def add(cls, values):
+ fields, taxes = cls._clean(cls, values)
+
+ if Productos.select().where(Productos.clave==fields['clave']).exists():
+ msg = 'Clave ya existe'
+ return {'ok': False, 'msg': msg}
+
+ obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
+
+ with database_proxy.transaction():
+ obj = Productos.create(**fields)
+ obj.impuestos = obj_taxes
+ row = {
+ 'id': obj.id,
+ 'clave': obj.clave,
+ 'descripcion': obj.descripcion,
+ 'unidad': obj.unidad.name,
+ 'valor_unitario': obj.valor_unitario,
+ }
+ data = {'ok': True, 'row': row, 'new': True}
+ return data
+
+ @classmethod
+ def actualizar(cls, values, id):
+ fields, taxes = cls._clean(cls, values)
+ obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
+ with database_proxy.transaction():
+ q = Productos.update(**fields).where(Productos.id==id)
+ try:
+ q.execute()
+ except IntegrityError:
+ msg = 'Ya existe un producto con esta clave'
+ data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
+ return data
+
+ obj = Productos.get(Productos.id==id)
+ obj.impuestos = obj_taxes
+ row = {
+ 'id': obj.id,
+ 'clave': obj.clave,
+ 'descripcion': obj.descripcion,
+ 'unidad': obj.unidad.name,
+ 'valor_unitario': obj.valor_unitario,
+ }
+ data = {'ok': True, 'row': row, 'new': False}
+ return data
+
+ @classmethod
+ def remove(cls, id):
+ count = (FacturasDetalle
+ .select(fn.COUNT(FacturasDetalle.id)).join(Productos)
+ .where(Productos.id==id)
+ .count()
+ )
+ if count:
+ return False
+
+ with database_proxy.transaction():
+ obj = Productos.get(Productos.id==id)
+ obj.impuestos.clear()
+ obj.tags.clear()
+ q = Productos.delete().where(Productos.id==id)
+ return bool(q.execute())
+
class Facturas(BaseModel):
cliente = ForeignKeyField(Socios)
@@ -553,6 +701,10 @@ def get_cp(cp):
return data
+def get_sat_key(key):
+ return util.get_sat_key('products', key)
+
+
def _init_values():
data = (
{'key': 'version', 'value': VERSION},
diff --git a/source/app/settings.py b/source/app/settings.py
index 733cd7b..5c1d76d 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -18,6 +18,7 @@ PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates'))
PATH_CP = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'cp.db'))
COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
+DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db'))
template_lookup = TemplateLookup(directories=[PATH_TEMPLATES],
input_encoding='utf-8',
diff --git a/source/db/sat.db b/source/db/sat.db
new file mode 100644
index 0000000..f86398d
Binary files /dev/null and b/source/db/sat.db differ
diff --git a/source/db/valores_iniciales.json b/source/db/valores_iniciales.json
index a6598b3..c5edd38 100644
--- a/source/db/valores_iniciales.json
+++ b/source/db/valores_iniciales.json
@@ -7,6 +7,13 @@
],
"regimen": "603"
},
+{
+ "tabla": "Categorias",
+ "datos": [
+ {"categoria": "Productos"},
+ {"categoria": "Servicios"}
+ ]
+},
{
"tabla": "SATImpuestos",
"datos": [
diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js
index f726fd2..e1ce3b7 100644
--- a/source/static/js/controller/main.js
+++ b/source/static/js/controller/main.js
@@ -1,7 +1,3 @@
-var PUBLICO = "Público en general";
-var RFC_PUBLICO = "XAXX010101000";
-var RFC_EXTRANJERO = "XEXX010101000";
-var PAIS = "México";
var controllers = {
@@ -30,17 +26,18 @@ var controllers = {
$$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click);
$$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click);
$$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click);
+ $$("chk_automatica").attachEvent("onChange", chk_automatica_change)
+ $$("valor_unitario").attachEvent("onChange", valor_unitario_change)
//~ Invoices
$$("cmd_new_invoice").attachEvent("onItemClick", cmd_new_invoice_click);
$$("cmd_edit_invoice").attachEvent("onItemClick", cmd_edit_invoice_click);
$$("cmd_delete_invoice").attachEvent("onItemClick", cmd_delete_invoice_click);
$$("cmd_save_invoice").attachEvent("onItemClick", cmd_save_invoice_click);
$$("cmd_cancel_invoice").attachEvent("onItemClick", cmd_cancel_invoice_click);
-
- //~ get_partners()
}
};
+
function get_partners(){
webix.ajax().get("/partners", {}, {
error: function(text, data, xhr) {
@@ -56,6 +53,24 @@ function get_partners(){
});
}
+
+function get_products(){
+ var grid = $$('grid_products')
+ webix.ajax().get('/products', {}, {
+ error: function(text, data, xhr) {
+ webix.message({type: 'error', text: 'Error al consultar'})
+ },
+ success: function(text, data, xhr) {
+ var values = data.json();
+ grid.clearAll();
+ if (values.ok){
+ grid.parse(values.rows, 'json');
+ };
+ }
+ });
+}
+
+
function menu_user_click(id, e, node){
if (id == 1){
window.location = '/logout';
@@ -71,5 +86,26 @@ function multi_change(prevID, nextID){
if(active == 'partners_home'){
get_partners()
}
+ return
}
+
+ if(nextID == 'app_products'){
+ active = $$('multi_products').getActiveId()
+ if(active == 'products_home'){
+ get_products()
+ }
+ return
+ }
+
+}
+
+
+function get_taxes(){
+ webix.ajax().sync().get('/values/taxes', function(text, data){
+ var values = data.json()
+ table_taxes.clear()
+ table_taxes.insert(values)
+ $$("grid_product_taxes").clearAll()
+ $$("grid_product_taxes").parse(values, 'json')
+ })
}
diff --git a/source/static/js/controller/products.js b/source/static/js/controller/products.js
index 80ee517..b63daee 100644
--- a/source/static/js/controller/products.js
+++ b/source/static/js/controller/products.js
@@ -1,43 +1,203 @@
function cmd_new_product_click(id, e, node){
-
+ $$('form_product').setValues({
+ id: 0, es_activo_producto: true})
+ add_config({'key': 'id_product', 'value': ''})
+ get_new_key()
+ get_taxes()
+ $$('grid_products').clearSelection()
+ $$('categoria').getList().load('/values/categorias')
+ $$('unidad').getList().load('/values/unidades')
$$("multi_products").setValue("product_new")
-
-};
+}
function cmd_edit_product_click(id, e, node){
-
- $$("multi_products").setValue("product_new")
-
-};
-
-
-function cmd_delete_product_click(id, e, node){
-
- webix.message({type:"success", text: "OK Delete"});
-
-};
-
-
-function cmd_save_product_click(id, e, node){
- var form = this.getFormView();
-
- if (!form.validate()) {
- webix.message({ type:"error", text:"Valores inválidos" });
+ var grid = $$('grid_products')
+ var row = grid.getSelectedItem()
+ if(row == undefined){
+ webix.message({type: 'error', text: 'Selecciona un Producto'})
return
}
- $$('form_product').clear();
- $$("multi_products").setValue("products_home")
- webix.message({type:"success", text: "Producto guardado correctamente"});
+ get_taxes()
+ $$('categoria').getList().load('/values/categorias')
+ $$('unidad').getList().load('/values/unidades')
+
+ webix.ajax().get('/products', {id:row['id']}, {
+ error: function(text, data, xhr) {
+ webix.message({type: 'error', text: 'Error al consultar'})
+ },
+ success: function(text, data, xhr){
+ var values = data.json()
+ $$('form_product').setValues(values.row)
+ add_config({'key': 'id_product', 'value': values.row.id})
+ for(i = 0; i < values.taxes.length; i++){
+ $$('grid_product_taxes').select(values.taxes[i], true)
+ }
+ }
+ })
+ $$('multi_products').setValue('product_new')
};
+function delete_product(id){
+ webix.ajax().del('/products', {id:id}, function(text, xml, xhr){
+ var msg = 'Producto eliminado correctamente'
+ if(xhr.status == 200){
+ $$('grid_products').remove(id)
+ webix.message({type:'success', text:msg})
+ }else{
+ msg = 'No se pudo eliminar'
+ webix.message({type:'error', text:msg})
+ }
+ })
+}
+
+
+function cmd_delete_product_click(id, e, node){
+ var row = $$('grid_products').getSelectedItem()
+ if (row == undefined){
+ webix.message({type:'error', text: 'Selecciona un Producto'})
+ return
+ }
+
+ var msg = '¿Estás seguro de eliminar el Producto?
'
+ msg += '(' + row['clave'] + ') ' + row['descripcion']
+ msg += '
ESTA ACCIÓN NO SE PUEDE DESHACER
Se recomienda '
+ msg += 'solo desactivar el producto en vez de eliminar'
+ webix.confirm({
+ title: 'Eliminar Producto',
+ ok: 'Si',
+ cancel: 'No',
+ type: 'confirm-error',
+ text: msg,
+ callback:function(result){
+ if (result){
+ delete_product(row['id'])
+ }
+ }
+ })
+}
+
+
+function validate_sat_key_product(key, text){
+ var result = false
+ webix.ajax().sync().get('/values/satkey', {key:key}, function(text, data){
+ result = data.json()
+ })
+ if(text){
+ if(result.ok){
+ return '' + result.text + ''
+ }else{
+ return '' + result.text + ''
+ }
+ }
+ return result.ok
+}
+
+
+function update_grid_products(values){
+ var msg = 'Producto agregado correctamente'
+ if(values.new){
+ $$('form_product').clear()
+ $$('grid_products').add(values.row)
+ }else{
+ msg = 'Producto actualizado correctamente'
+ $$("grid_products").updateItem(values.row['id'], values.row)
+ }
+ $$('multi_products').setValue('products_home')
+ webix.message({type: 'success', text: msg})
+}
+
+
+function cmd_save_product_click(id, e, node){
+ var msg = ''
+ var form = this.getFormView()
+
+ if(!form.validate()){
+ webix.message({type: 'error', text: 'Valores inválidos'})
+ return
+ }
+
+ var rows = $$('grid_product_taxes').getSelectedId(true, true)
+ if (rows.length == 0){
+ webix.message({type: 'error', text: 'Selecciona un impuesto'})
+ return
+ }
+
+ var values = form.getValues();
+
+ if (!validate_sat_key_product(values.clave_sat, false)){
+ webix.message({ type:'error', text:'La clave SAT no existe' })
+ return
+ }
+
+ values['taxes'] = JSON.stringify(rows)
+ webix.ajax().sync().post('products', values, {
+ error:function(text, data, XmlHttpRequest){
+ msg = 'Ocurrio un error, consulta a soporte técnico'
+ webix.message({type: 'error', text: msg})
+ },
+ success:function(text, data, XmlHttpRequest){
+ var values = data.json();
+ if (values.ok) {
+ update_grid_products(values)
+ }else{
+ webix.message({type:'error', text:values.msg})
+ }
+ }
+ })
+}
+
+
function cmd_cancel_product_click(id, e, node){
$$("multi_products").setValue("products_home")
};
+
+
+function chk_automatica_change(new_value, old_value){
+ var value = Boolean(new_value)
+ if (value){
+ var value = get_config('id_product')
+ if(value){
+ $$("clave").setValue(value)
+ $$("clave").refresh()
+ }else{
+ get_new_key()
+ }
+ $$("clave").config.readonly = true
+ $$('form_product').focus('clave_sat')
+ } else {
+ $$("clave").setValue('')
+ $$("clave").config.readonly = false
+ $$('form_product').focus('clave')
+ }
+ $$("clave").refresh()
+}
+
+
+function get_new_key(){
+ webix.ajax().get('/values/newkey', {
+ error: function(text, data, xhr) {
+ webix.message({type:'error', text: text})
+ },
+ success: function(text, data, xhr) {
+ var values = data.json();
+ $$("clave").setValue(values.value)
+ $$("clave").refresh()
+ }
+ })
+}
+
+
+function valor_unitario_change(new_value, old_value){
+ if(!isFinite(new_value)){
+ this.config.value = old_value
+ this.refresh()
+ }
+}
diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js
index 952b7e0..9cd8fea 100644
--- a/source/static/js/controller/util.js
+++ b/source/static/js/controller/util.js
@@ -1,3 +1,43 @@
+var PUBLICO = "Público en general";
+var RFC_PUBLICO = "XAXX010101000";
+var RFC_EXTRANJERO = "XEXX010101000";
+var PAIS = "México";
+
+
+var db = new loki('data.db');
+var table_config = db.addCollection('config')
+var table_taxes = db.addCollection('taxes')
+var table_pt = db.addCollection('productstaxes')
+var table_totals = db.addCollection('totals', {unique: ['tax']})
+
+
+function show(values){
+ webix.message(JSON.stringify(values, null, 2))
+}
+
+
+webix.protoUI({
+ $cssName: "text",
+ name: "currency",
+ $init:function(){
+ this.attachEvent("onItemClick", function(){
+ this.$setValue(this.config.raw, true)
+ })
+ this.attachEvent("onBlur", function(){
+ this.$setValue(this.config.value)
+ })
+ },
+ $render:function(){
+ this.$setValue(this.config.value)
+ },
+ $setValue:function(value, raw){
+ this.config.raw = value
+ if(!raw){
+ value = webix.i18n.priceFormat(value)
+ }
+ this.getInputNode().value = value
+ }
+}, webix.ui.text)
webix.ui.datafilter.rowCount = webix.extend({
@@ -68,3 +108,24 @@ function validate_email(email){
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(email)
}
+
+
+function add_config(args){
+ var key = table_config.findOne({key: args.key})
+ if(key===null){
+ table_config.insert(args)
+ }else{
+ key.value = args.value
+ table_config.update(key)
+ }
+}
+
+
+function get_config(value){
+ var key = table_config.findOne({key: value})
+ if(key===null){
+ return ''
+ }else{
+ return key.value
+ }
+}
diff --git a/source/static/js/lokijs.min.js b/source/static/js/lokijs.min.js
new file mode 100644
index 0000000..cf245f0
--- /dev/null
+++ b/source/static/js/lokijs.min.js
@@ -0,0 +1,3 @@
+(function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.loki=factory()}})(this,function(){return function(){"use strict";var hasOwnProperty=Object.prototype.hasOwnProperty;var Utils={copyProperties:function(src,dest){var prop;for(prop in src){dest[prop]=src[prop]}},resolveTransformObject:function(subObj,params,depth){var prop,pname;if(typeof depth!=="number"){depth=0}if(++depth>=10)return subObj;for(prop in subObj){if(typeof subObj[prop]==="string"&&subObj[prop].indexOf("[%lktxp]")===0){pname=subObj[prop].substring(8);if(params.hasOwnProperty(pname)){subObj[prop]=params[pname]}}else if(typeof subObj[prop]==="object"){subObj[prop]=Utils.resolveTransformObject(subObj[prop],params,depth)}}return subObj},resolveTransformParams:function(transform,params){var idx,clonedStep,resolvedTransform=[];if(typeof params==="undefined")return transform;for(idx=0;idxcv2)return false;return equal}if(cv1===cv1&&cv2!==cv2){return true}if(cv2===cv2&&cv1!==cv1){return false}if(prop1prop2)return false;if(prop1==prop2)return equal;cv1=prop1.toString();cv2=prop2.toString();if(cv1t2}}cv1=Number(prop1);cv2=Number(prop2);if(cv1===cv1&&cv2===cv2){if(cv1>cv2)return true;if(cv1prop2)return true;if(prop1cv2){return true}if(cv1==cv2){return equal}return false}function sortHelper(prop1,prop2,desc){if(aeqHelper(prop1,prop2))return 0;if(ltHelper(prop1,prop2,false)){return desc?1:-1}if(gtHelper(prop1,prop2,false)){return desc?-1:1}return 0}function compoundeval(properties,obj1,obj2){var res=0;var prop,field,val1,val2,arr;for(var i=0,len=properties.length;i=paths.length){valueFound=fun(element,value)}else if(Array.isArray(element)){for(var index=0,len=element.length;index=0){return this.serializeCollection({delimited:options.delimited,delimiter:options.delimiter,collectionIndex:options.partition})}dbcopy=new Loki(this.filename);dbcopy.loadJSONObject(this);for(idx=0;idxcollCount){done=true}}else{currObject=JSON.parse(workarray[lineIndex]);cdb.collections[collIndex].data.push(currObject)}workarray[lineIndex++]=null}return cdb};Loki.prototype.deserializeCollection=function(destructuredSource,options){var workarray=[];var idx,len;options=options||{};if(!options.hasOwnProperty("partitioned")){options.partitioned=false}if(!options.hasOwnProperty("delimited")){options.delimited=true}if(!options.hasOwnProperty("delimiter")){options.delimiter=this.options.destructureDelimiter}if(options.delimited){workarray=destructuredSource.split(options.delimiter);workarray.pop()}else{workarray=destructuredSource}len=workarray.length;for(idx=0;idx=cdlen)doneWithPartition=true}if(pageLen>=this.options.pageSize)doneWithPage=true;if(!doneWithPage||doneWithPartition){pageBuilder+=this.options.delimiter;pageLen+=delimlen}if(doneWithPartition||doneWithPage){this.adapter.saveDatabase(keyname,pageBuilder,pageSaveCallback);return}}};function LokiFsAdapter(){this.fs=require("fs")}LokiFsAdapter.prototype.loadDatabase=function loadDatabase(dbname,callback){var self=this;this.fs.stat(dbname,function(err,stats){if(!err&&stats.isFile()){self.fs.readFile(dbname,{encoding:"utf8"},function readFileCallback(err,data){if(err){callback(new Error(err))}else{callback(data)}})}else{callback(null)}})};LokiFsAdapter.prototype.saveDatabase=function saveDatabase(dbname,dbstring,callback){var self=this;var tmpdbname=dbname+"~";this.fs.writeFile(tmpdbname,dbstring,function writeFileCallback(err){if(err){callback(new Error(err))}else{self.fs.rename(tmpdbname,dbname,callback)}})};LokiFsAdapter.prototype.deleteDatabase=function deleteDatabase(dbname,callback){this.fs.unlink(dbname,function deleteDatabaseCallback(err){if(err){callback(new Error(err))}else{callback()}})};function LokiLocalStorageAdapter(){}LokiLocalStorageAdapter.prototype.loadDatabase=function loadDatabase(dbname,callback){if(localStorageAvailable()){callback(localStorage.getItem(dbname))}else{callback(new Error("localStorage is not available"))}};LokiLocalStorageAdapter.prototype.saveDatabase=function saveDatabase(dbname,dbstring,callback){if(localStorageAvailable()){localStorage.setItem(dbname,dbstring);callback(null)}else{callback(new Error("localStorage is not available"))}};LokiLocalStorageAdapter.prototype.deleteDatabase=function deleteDatabase(dbname,callback){if(localStorageAvailable()){localStorage.removeItem(dbname);callback(null)}else{callback(new Error("localStorage is not available"))}};Loki.prototype.throttledSaveDrain=function(callback,options){var self=this;var now=(new Date).getTime();if(!this.throttledSaves){callback(true)}options=options||{};if(!options.hasOwnProperty("recursiveWait")){options.recursiveWait=true}if(!options.hasOwnProperty("recursiveWaitLimit")){options.recursiveWaitLimit=false}if(!options.hasOwnProperty("recursiveWaitLimitDuration")){options.recursiveWaitLimitDuration=2e3}if(!options.hasOwnProperty("started")){options.started=(new Date).getTime()}if(this.throttledSaves&&this.throttledSavePending){if(options.recursiveWait){this.throttledCallbacks.push(function(){if(self.throttledSavePending){if(options.recursiveWaitLimit&&now-options.started>options.recursiveWaitLimitDuration){callback(false);return}self.throttledSaveDrain(callback,options);return}else{callback(true);return}})}else{this.throttledCallbacks.push(callback);return}}else{callback(true)}};Loki.prototype.loadDatabaseInternal=function(options,callback){var cFun=callback||function(err,data){if(err){throw err}},self=this;if(this.persistenceAdapter!==null){
+this.persistenceAdapter.loadDatabase(this.filename,function loadDatabaseCallback(dbString){if(typeof dbString==="string"){var parseSuccess=false;try{self.loadJSON(dbString,options||{});parseSuccess=true}catch(err){cFun(err)}if(parseSuccess){cFun(null);self.emit("loaded","database "+self.filename+" loaded")}}else{if(!dbString){cFun(null);self.emit("loaded","empty database "+self.filename+" loaded");return}if(dbString instanceof Error){cFun(dbString);return}if(typeof dbString==="object"){self.loadJSONObject(dbString,options||{});cFun(null);self.emit("loaded","database "+self.filename+" loaded");return}cFun("unexpected adapter response : "+dbString)}})}else{cFun(new Error("persistenceAdapter not configured"))}};Loki.prototype.loadDatabase=function(options,callback){var self=this;if(!this.throttledSaves){this.loadDatabaseInternal(options,callback);return}this.throttledSaveDrain(function(success){if(success){self.throttledSavePending=true;self.loadDatabaseInternal(options,function(err){if(self.throttledCallbacks.length===0){self.throttledSavePending=false}else{self.saveDatabase()}if(typeof callback==="function"){callback(err)}});return}else{if(typeof callback==="function"){callback(new Error("Unable to pause save throttling long enough to read database"))}}},options)};Loki.prototype.saveDatabaseInternal=function(callback){var cFun=callback||function(err){if(err){throw err}return},self=this;if(this.persistenceAdapter!==null){if(this.persistenceAdapter.mode==="reference"&&typeof this.persistenceAdapter.exportDatabase==="function"){this.persistenceAdapter.exportDatabase(this.filename,this.copy({removeNonSerializable:true}),function exportDatabaseCallback(err){self.autosaveClearFlags();cFun(err)})}else{this.persistenceAdapter.saveDatabase(this.filename,self.serialize(),function saveDatabasecallback(err){self.autosaveClearFlags();cFun(err)})}}else{cFun(new Error("persistenceAdapter not configured"))}};Loki.prototype.saveDatabase=function(callback){if(!this.throttledSaves){this.saveDatabaseInternal(callback);return}if(this.throttledSavePending){this.throttledCallbacks.push(callback);return}var localCallbacks=this.throttledCallbacks;this.throttledCallbacks=[];localCallbacks.unshift(callback);this.throttledSavePending=true;var self=this;this.saveDatabaseInternal(function(err){self.throttledSavePending=false;localCallbacks.forEach(function(pcb){if(typeof pcb==="function"){setTimeout(function(){pcb(err)},1)}});if(self.throttledCallbacks.length>0){self.saveDatabase()}})};Loki.prototype.save=Loki.prototype.saveDatabase;Loki.prototype.deleteDatabase=function(options,callback){var cFun=callback||function(err,data){if(err){throw err}};if(typeof options==="function"&&!callback){cFun=options}if(this.persistenceAdapter!==null){this.persistenceAdapter.deleteDatabase(this.filename,function deleteDatabaseCallback(err){cFun(err)})}else{cFun(new Error("persistenceAdapter not configured"))}};Loki.prototype.autosaveDirty=function(){for(var idx=0;idx0){this.filteredrows=[]}this.filterInitialized=false;return this};Resultset.prototype.toJSON=function(){var copy=this.copy();copy.collection=null;return copy};Resultset.prototype.limit=function(qty){if(!this.filterInitialized&&this.filteredrows.length===0){this.filteredrows=this.collection.prepareFullDocIndex()}var rscopy=new Resultset(this.collection);rscopy.filteredrows=this.filteredrows.slice(0,qty);rscopy.filterInitialized=true;return rscopy};Resultset.prototype.offset=function(pos){if(!this.filterInitialized&&this.filteredrows.length===0){this.filteredrows=this.collection.prepareFullDocIndex()}var rscopy=new Resultset(this.collection);rscopy.filteredrows=this.filteredrows.slice(pos);rscopy.filterInitialized=true;return rscopy};Resultset.prototype.copy=function(){var result=new Resultset(this.collection);if(this.filteredrows.length>0){result.filteredrows=this.filteredrows.slice()}result.filterInitialized=this.filterInitialized;return result};Resultset.prototype.branch=Resultset.prototype.copy;Resultset.prototype.transform=function(transform,parameters){var idx,step,rs=this;if(typeof transform==="string"){if(this.collection.transforms.hasOwnProperty(transform)){transform=this.collection.transforms[transform]}}if(typeof transform!=="object"||!Array.isArray(transform)){throw new Error("Invalid transform")}if(typeof parameters!=="undefined"){transform=Utils.resolveTransformParams(transform,parameters)}for(idx=0;idx1){return this.find({$and:filters},firstOnly)}}if(!property||queryObject==="getAll"){if(firstOnly){this.filteredrows=this.collection.data.length>0?[0]:[];this.filterInitialized=true}return this}if(property==="$and"||property==="$or"){this[property](queryObjectOp);if(firstOnly&&this.filteredrows.length>1){this.filteredrows=this.filteredrows.slice(0,1)}return this}if(queryObjectOp===null||(typeof queryObjectOp!=="object"||queryObjectOp instanceof Date)){operator="$eq";value=queryObjectOp}else if(typeof queryObjectOp==="object"){for(key in queryObjectOp){if(hasOwnProperty.call(queryObjectOp,key)){operator=key;value=queryObjectOp[key];break}}}else{throw new Error("Do not know what you want to do.")}if(operator==="$regex"){if(Array.isArray(value)){value=new RegExp(value[0],value[1])}else if(!(value instanceof RegExp)){value=new RegExp(value)}}var usingDotNotation=property.indexOf(".")!==-1;var doIndexCheck=!usingDotNotation&&!this.filterInitialized;if(doIndexCheck&&this.collection.binaryIndices[property]&&indexedOps[operator]){if(this.collection.adaptiveBinaryIndices!==true){this.collection.ensureIndex(property)}searchByIndex=true;index=this.collection.binaryIndices[property]}var fun=LokiOps[operator];var t=this.collection.data;var i=0,len=0;var filter,rowIdx=0;if(this.filterInitialized){filter=this.filteredrows;len=filter.length;if(usingDotNotation){property=property.split(".");for(i=0;i=0){this.filterPipeline[idx]=filter;return this.reapplyFilters()}this.cachedresultset=null;if(this.options.persistent){this.resultdata=[];this.resultsdirty=true}this._addFilter(filter);if(this.sortFunction||this.sortCriteria){this.queueSortPhase()}else{this.queueRebuildEvent()}return this};DynamicView.prototype.applyFind=function(query,uid){this.applyFilter({type:"find",val:query,uid:uid});return this};DynamicView.prototype.applyWhere=function(fun,uid){this.applyFilter({type:"where",val:fun,uid:uid});return this};DynamicView.prototype.removeFilter=function(uid){var idx=this._indexOfFilterWithId(uid);if(idx<0){throw new Error("Dynamic view does not contain a filter with ID: "+uid)}this.filterPipeline.splice(idx,1);this.reapplyFilters();return this};DynamicView.prototype.count=function(){if(this.resultsdirty){this.resultdata=this.resultset.data()}return this.resultset.count()};DynamicView.prototype.data=function(options){if(this.sortDirty||this.resultsdirty){this.performSortPhase({suppressRebuildEvent:true})}return this.options.persistent?this.resultdata:this.resultset.data(options)};DynamicView.prototype.queueRebuildEvent=function(){if(this.rebuildPending){return}this.rebuildPending=true;var self=this;setTimeout(function(){if(self.rebuildPending){self.rebuildPending=false;self.emit("rebuild",self)}},this.options.minRebuildInterval)};DynamicView.prototype.queueSortPhase=function(){if(this.sortDirty){return}this.sortDirty=true;var self=this;if(this.options.sortPriority==="active"){setTimeout(function(){self.performSortPhase()},this.options.minRebuildInterval)}else{this.queueRebuildEvent()}};DynamicView.prototype.performSortPhase=function(options){if(!this.sortDirty&&!this.resultsdirty){return}options=options||{};if(this.sortDirty){if(this.sortFunction){this.resultset.sort(this.sortFunction)}else if(this.sortCriteria){this.resultset.compoundsort(this.sortCriteria)}this.sortDirty=false}if(this.options.persistent){this.resultdata=this.resultset.data();this.resultsdirty=false}if(!options.suppressRebuildEvent){this.emit("rebuild",this)}};DynamicView.prototype.evaluateDocument=function(objIndex,isNew){if(!this.resultset.filterInitialized){if(this.options.persistent){this.resultdata=this.resultset.data()}if(this.sortFunction||this.sortCriteria){this.queueSortPhase()}else{this.queueRebuildEvent()}return}var ofr=this.resultset.filteredrows;var oldPos=isNew?-1:ofr.indexOf(+objIndex);var oldlen=ofr.length;var evalResultset=new Resultset(this.collection);evalResultset.filteredrows=[objIndex];evalResultset.filterInitialized=true;var filter;for(var idx=0,len=this.filterPipeline.length;idxobjIndex){ofr[idx]--}}};DynamicView.prototype.mapReduce=function(mapFunction,reduceFunction){try{return reduceFunction(this.data().map(mapFunction))}catch(err){throw err}};function Collection(name,options){this.name=name;this.data=[];this.idIndex=[];this.binaryIndices={};this.constraints={unique:{},exact:{}};this.uniqueNames=[];this.transforms={};this.objType=name;this.dirty=true;this.cachedIndex=null;this.cachedBinaryIndex=null;this.cachedData=null;var self=this;options=options||{};if(options.hasOwnProperty("unique")){if(!Array.isArray(options.unique)){options.unique=[options.unique]}options.unique.forEach(function(prop){self.uniqueNames.push(prop);self.constraints.unique[prop]=new UniqueIndex(prop)})}if(options.hasOwnProperty("exact")){options.exact.forEach(function(prop){self.constraints.exact[prop]=new ExactIndex(prop)})}this.adaptiveBinaryIndices=options.hasOwnProperty("adaptiveBinaryIndices")?options.adaptiveBinaryIndices:true;this.transactional=options.hasOwnProperty("transactional")?options.transactional:false;this.cloneObjects=options.hasOwnProperty("clone")?options.clone:false;this.cloneMethod=options.hasOwnProperty("cloneMethod")?options.cloneMethod:"parse-stringify";this.asyncListeners=options.hasOwnProperty("asyncListeners")?options.asyncListeners:false;this.disableChangesApi=options.hasOwnProperty("disableChangesApi")?options.disableChangesApi:true;this.disableDeltaChangesApi=options.hasOwnProperty("disableDeltaChangesApi")?options.disableDeltaChangesApi:true;if(this.disableChangesApi){this.disableDeltaChangesApi=true}this.autoupdate=options.hasOwnProperty("autoupdate")?options.autoupdate:false;this.serializableIndices=options.hasOwnProperty("serializableIndices")?options.serializableIndices:true;this.ttl={age:null,ttlInterval:null,daemon:null};this.setTTL(options.ttl||-1,options.ttlInterval);this.maxId=0;this.DynamicViews=[];this.events={insert:[],update:[],"pre-insert":[],"pre-update":[],close:[],flushbuffer:[],error:[],delete:[],warning:[]};this.changes=[];this.ensureId();var indices=[];if(options&&options.indices){if(Object.prototype.toString.call(options.indices)==="[object Array]"){indices=options.indices}else if(typeof options.indices==="string"){indices=[options.indices]}else{throw new TypeError("Indices needs to be a string or an array of strings")}}for(var idx=0;idx=0||propertyName=="$loki"||propertyName=="meta"){delta[propertyName]=newObject[propertyName]}else{var propertyDelta=getObjectDelta(oldObject[propertyName],newObject[propertyName]);if(typeof propertyDelta!=="undefined"&&propertyDelta!={}){delta[propertyName]=propertyDelta}}}}return Object.keys(delta).length===0?undefined:delta}else{return oldObject===newObject?undefined:newObject}}this.getObjectDelta=getObjectDelta;function flushChanges(){self.changes=[]}this.getChanges=function(){return self.changes};this.flushChanges=flushChanges;function insertMeta(obj){var len,idx;if(!obj){return}if(Array.isArray(obj)){len=obj.length;for(idx=0;idx>1;id=typeof id==="number"?id:parseInt(id,10);if(isNaN(id)){throw new TypeError("Passed id is not an integer")}while(data[min]>1;if(data[mid]dataPosition){index[idx]--}}};Collection.prototype.calculateRangeStart=function(prop,val,adaptive){var rcd=this.data;var index=this.binaryIndices[prop].values;var min=0;var max=index.length-1;var mid=0;if(index.length===0){return-1}var minVal=rcd[index[min]][prop];var maxVal=rcd[index[max]][prop];while(min>1;if(ltHelper(rcd[index[mid]][prop],val,false)){min=mid+1}else{max=mid}}var lbound=min;if(aeqHelper(val,rcd[index[lbound]][prop])){return lbound}if(ltHelper(val,rcd[index[lbound]][prop],false)){return adaptive?lbound:lbound-1}return adaptive?lbound+1:lbound};Collection.prototype.calculateRangeEnd=function(prop,val){var rcd=this.data;var index=this.binaryIndices[prop].values;var min=0;var max=index.length-1;var mid=0;if(index.length===0){return-1}var minVal=rcd[index[min]][prop];var maxVal=rcd[index[max]][prop];while(min>1;if(ltHelper(val,rcd[index[mid]][prop],false)){max=mid}else{min=mid+1}}var ubound=max;if(aeqHelper(val,rcd[index[ubound]][prop])){return ubound}if(gtHelper(val,rcd[index[ubound]][prop],false)){return ubound+1}if(aeqHelper(val,rcd[index[ubound-1]][prop])){return ubound-1}return ubound};Collection.prototype.calculateRange=function(op,prop,val){var rcd=this.data;var index=this.binaryIndices[prop].values;var min=0;var max=index.length-1;var mid=0;var lbound,lval;var ubound,uval;if(rcd.length===0){return[0,-1]}var minVal=rcd[index[min]][prop];var maxVal=rcd[index[max]][prop];switch(op){case"$eq":case"$aeq":if(ltHelper(val,minVal,false)||gtHelper(val,maxVal,false)){return[0,-1]}break;case"$dteq":if(ltHelper(val,minVal,false)||gtHelper(val,maxVal,false)){return[0,-1]}break;case"$gt":if(gtHelper(val,maxVal,true)){return[0,-1]}if(gtHelper(minVal,val,false)){return[min,max]}break;case"$gte":if(gtHelper(val,maxVal,false)){return[0,-1]}if(gtHelper(minVal,val,true)){return[min,max]}break;case"$lt":if(ltHelper(val,minVal,true)){return[0,-1]}if(ltHelper(maxVal,val,false)){return[min,max]}break;case"$lte":if(ltHelper(val,minVal,false)){return[0,-1]}if(ltHelper(maxVal,val,true)){return[min,max]}break;case"$between":if(gtHelper(val[0],maxVal,false)){return[0,-1]}if(ltHelper(val[1],minVal,false)){return[0,-1]}lbound=this.calculateRangeStart(prop,val[0]);ubound=this.calculateRangeEnd(prop,val[1]);if(lbound<0)lbound++;if(ubound>max)ubound--;if(!gtHelper(rcd[index[lbound]][prop],val[0],true))lbound++;if(!ltHelper(rcd[index[ubound]][prop],val[1],true))ubound--;if(ubounddeepProperty(this.data[i],field,deep)){min=deepProperty(this.data[i],field,deep);result.index=this.data[i].$loki}}else{min=deepProperty(this.data[i],field,deep);result.index=this.data[i].$loki}}result.value=min;return result};Collection.prototype.extractNumerical=function(field){return this.extract(field).map(parseBase10).filter(Number).filter(function(n){return!isNaN(n)})};Collection.prototype.avg=function(field){return average(this.extractNumerical(field))};Collection.prototype.stdDev=function(field){return standardDeviation(this.extractNumerical(field))};Collection.prototype.mode=function(field){var dict={},data=this.extract(field);data.forEach(function(obj){if(dict[obj]){dict[obj]+=1}else{dict[obj]=1}});var max,prop,mode;for(prop in dict){if(max){if(max0){root=root[pieces.shift()]}return root}function binarySearch(array,item,fun){var lo=0,hi=array.length,compared,mid;while(lo>1;compared=fun.apply(null,[item,array[mid]]);if(compared===0){return{found:true,index:mid}}else if(compared<0){hi=mid}else{lo=mid+1}}return{found:false,index:hi}}function BSonSort(fun){return function(array,item){return binarySearch(array,item,fun)}}function KeyValueStore(){}KeyValueStore.prototype={keys:[],values:[],sort:function(a,b){return ab?1:0},setSort:function(fun){this.bs=new BSonSort(fun)},bs:function(){return new BSonSort(this.sort)},set:function(key,value){var pos=this.bs(this.keys,key);if(pos.found){this.values[pos.index]=value}else{this.keys.splice(pos.index,0,key);this.values.splice(pos.index,0,value)}},get:function(key){return this.values[binarySearch(this.keys,key,this.sort).index]}};function UniqueIndex(uniqueField){this.field=uniqueField;this.keyMap={};this.lokiMap={}}UniqueIndex.prototype.keyMap={};UniqueIndex.prototype.lokiMap={};UniqueIndex.prototype.set=function(obj){var fieldValue=obj[this.field];if(fieldValue!==null&&typeof fieldValue!=="undefined"){if(this.keyMap[fieldValue]){throw new Error("Duplicate key for property "+this.field+": "+fieldValue)}else{this.keyMap[fieldValue]=obj;this.lokiMap[obj.$loki]=fieldValue}}};UniqueIndex.prototype.get=function(key){return this.keyMap[key]};UniqueIndex.prototype.byId=function(id){return this.keyMap[this.lokiMap[id]]};UniqueIndex.prototype.update=function(obj,doc){if(this.lokiMap[obj.$loki]!==doc[this.field]){var old=this.lokiMap[obj.$loki];this.set(doc);this.keyMap[old]=undefined}else{this.keyMap[obj[this.field]]=doc}};UniqueIndex.prototype.remove=function(key){var obj=this.keyMap[key];if(obj!==null&&typeof obj!=="undefined"){this.keyMap[key]=undefined;this.lokiMap[obj.$loki]=undefined}else{throw new Error("Key is not in unique index: "+this.field)}};UniqueIndex.prototype.clear=function(){this.keyMap={};this.lokiMap={}};function ExactIndex(exactField){this.index={};this.field=exactField}ExactIndex.prototype={set:function add(key,val){if(this.index[key]){this.index[key].push(val)}else{this.index[key]=[val]}},remove:function remove(key,val){var idxSet=this.index[key];for(var i in idxSet){if(idxSet[i]==val){idxSet.splice(i,1)}}if(idxSet.length<1){this.index[key]=undefined}},get:function get(key){return this.index[key]},clear:function clear(key){this.index={}}};function SortedIndex(sortedField){this.field=sortedField}SortedIndex.prototype={keys:[],values:[],sort:function(a,b){return ab?1:0},bs:function(){return new BSonSort(this.sort)},setSort:function(fun){this.bs=new BSonSort(fun)},set:function(key,value){var pos=binarySearch(this.keys,key,this.sort);if(pos.found){this.values[pos.index].push(value)}else{this.keys.splice(pos.index,0,key);this.values.splice(pos.index,0,[value])}},get:function(key){var bsr=binarySearch(this.keys,key,this.sort);if(bsr.found){return this.values[bsr.index]}else{return[]}},getLt:function(key){var bsr=binarySearch(this.keys,key,this.sort);var pos=bsr.index;if(bsr.found)pos--;return this.getAll(key,0,pos)},getGt:function(key){var bsr=binarySearch(this.keys,key,this.sort);var pos=bsr.index;if(bsr.found)pos++;return this.getAll(key,pos,this.keys.length)},getAll:function(key,start,end){var results=[];for(var i=start;i
+
<%block name="media"/>