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"/>