diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py
index e1b9078ec3f..86166ec0125 100644
--- a/addons/account/__openerp__.py
+++ b/addons/account/__openerp__.py
@@ -19,11 +19,11 @@
#
##############################################################################
{
- "name" : "eInvoicing",
- "version" : "1.1",
- "author" : "OpenERP SA",
- "category": 'Accounting & Finance',
- "description": """
+ 'name' : 'eInvoicing',
+ 'version' : '1.1',
+ 'author' : 'OpenERP SA',
+ 'category' : 'Accounting & Finance',
+ 'description' : """
Accounting and Financial Management.
====================================
@@ -44,14 +44,13 @@ Creates a dashboard for accountants that includes:
* Company Analysis
* Graph of Treasury
-The processes like maintaining of general ledger is done through the defined financial Journals (entry move line orgrouping is maintained through journal) for a particular
-financial year and for preparation of vouchers there is a module named account_voucher.
+The processes like maintaining of general ledger is done through the defined financial Journals (entry move line orgrouping is maintained through journal)
+for a particular financial year and for preparation of vouchers there is a module named account_voucher.
""",
'website': 'http://www.openerp.com',
'images' : ['images/accounts.jpeg','images/bank_statement.jpeg','images/cash_register.jpeg','images/chart_of_accounts.jpeg','images/customer_invoice.jpeg','images/journal_entries.jpeg'],
- 'init_xml': [],
- "depends" : ["base_setup", "product", "analytic", "process", "board", "edi"],
- 'update_xml': [
+ 'depends' : ['base_setup', 'product', 'analytic', 'process', 'board', 'edi'],
+ 'data': [
'security/account_security.xml',
'security/ir.model.access.csv',
'account_menuitem.xml',
@@ -122,12 +121,12 @@ financial year and for preparation of vouchers there is a module named account_v
'ir_sequence_view.xml',
'company_view.xml',
'board_account_view.xml',
- "edi/invoice_action_data.xml",
- "account_bank_view.xml",
- "res_config_view.xml",
- "account_pre_install.yml"
+ 'edi/invoice_action_data.xml',
+ 'account_bank_view.xml',
+ 'res_config_view.xml',
+ 'account_pre_install.yml'
],
- 'demo_xml': [
+ 'demo': [
'demo/account_demo.xml',
'project/project_demo.xml',
'project/analytic_account_demo.xml',
diff --git a/addons/account/account.py b/addons/account/account.py
index fa30b20f2ca..907e6207380 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -582,6 +582,8 @@ class account_account(osv.osv):
def name_get(self, cr, uid, ids, context=None):
if not ids:
return []
+ if isinstance(ids, (int, long)):
+ ids = [ids]
reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
res = []
for record in reads:
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index 012bde42eff..7bc9ceaaf61 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -1127,7 +1127,7 @@ class account_invoice(osv.osv):
return map(lambda x: (0,0,x), lines)
def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None):
- invoices = self.read(cr, uid, ids, ['name', 'type', 'number', 'reference', 'comment', 'date_due', 'partner_id', 'partner_contact', 'partner_insite', 'partner_ref', 'payment_term', 'account_id', 'currency_id', 'invoice_line', 'tax_line', 'journal_id'])
+ invoices = self.read(cr, uid, ids, ['name', 'type', 'number', 'reference', 'comment', 'date_due', 'partner_id', 'partner_contact', 'partner_insite', 'partner_ref', 'payment_term', 'account_id', 'currency_id', 'invoice_line', 'tax_line', 'journal_id', 'company_id'])
obj_invoice_line = self.pool.get('account.invoice.line')
obj_invoice_tax = self.pool.get('account.invoice.tax')
obj_journal = self.pool.get('account.journal')
@@ -1175,7 +1175,7 @@ class account_invoice(osv.osv):
'name': description,
})
# take the id part of the tuple returned for many2one fields
- for field in ('partner_id',
+ for field in ('partner_id', 'company_id',
'account_id', 'currency_id', 'payment_term', 'journal_id'):
invoice[field] = invoice[field] and invoice[field][0]
# create the new invoice
diff --git a/addons/account/test/account_change_currency.yml b/addons/account/test/account_change_currency.yml
index 1f1a412c19e..5f06a20b1ab 100644
--- a/addons/account/test/account_change_currency.yml
+++ b/addons/account/test/account_change_currency.yml
@@ -7,7 +7,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 450.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account/test/account_invoice_state.yml b/addons/account/test/account_invoice_state.yml
index 6553635a781..7b995150af0 100644
--- a/addons/account/test/account_invoice_state.yml
+++ b/addons/account/test/account_invoice_state.yml
@@ -7,7 +7,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 450.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account/test/account_sequence_test.yml b/addons/account/test/account_sequence_test.yml
index 3bc4a778794..74dcbce56ba 100644
--- a/addons/account/test/account_sequence_test.yml
+++ b/addons/account/test/account_sequence_test.yml
@@ -20,7 +20,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
- name: '[PC3] Medium PC'
+ name: '[PC-DEM] PC on Demand'
price_unit: 900.0
quantity: 10.0
product_id: product.product_product_5
diff --git a/addons/account/test/account_supplier_invoice.yml b/addons/account/test/account_supplier_invoice.yml
index ccebd2ea90c..9c5ce2dea3e 100644
--- a/addons/account/test/account_supplier_invoice.yml
+++ b/addons/account/test/account_supplier_invoice.yml
@@ -28,7 +28,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_expense
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 300.0
product_id: product.product_product_3
quantity: 10.0
diff --git a/addons/account/test/test_edi_invoice.yml b/addons/account/test/test_edi_invoice.yml
index 516be311cf8..80dc098e770 100644
--- a/addons/account/test/test_edi_invoice.yml
+++ b/addons/account/test/test_edi_invoice.yml
@@ -24,7 +24,7 @@
uos_id: 1
quantity: 5.0
price_unit: 100.0
- name: 'Medium PC'
+ name: 'PC on Demand'
account_id: account.a_pay
tax_line:
- name: sale tax
@@ -104,9 +104,9 @@
"__model": "account.invoice.line",
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so",
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
- "name": "Basic PC",
+ "name": "PC Assemble SC234",
"price_unit": 10.0,
- "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PC1] Basic PC"],
+ "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
"quantity": 1.0
},
{
@@ -114,9 +114,9 @@
"__model": "account.invoice.line",
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5",
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
- "name": "Medium PC",
+ "name": "PC on Demand",
"price_unit": 100.0,
- "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC3] Medium PC"],
+ "product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
"quantity": 5.0
}]
}
@@ -137,12 +137,12 @@
assert invoice_new.type == 'in_invoice', "Invoice type was not set properly"
assert len(invoice_new.invoice_line) == 2, "invoice lines are not same"
for inv_line in invoice_new.invoice_line:
- if inv_line.name == 'Basic PC':
+ if inv_line.name == 'PC Assemble SC234':
assert inv_line.uos_id.name == "Unit" , "uom is not same"
assert inv_line.price_unit == 10 , "price unit is not same"
assert inv_line.quantity == 1 , "product qty is not same"
assert inv_line.price_subtotal == 10, "price sub total is not same"
- elif inv_line.name == 'Medium PC':
+ elif inv_line.name == 'PC on Demand':
assert inv_line.uos_id.name == "Unit" , "uom is not same"
assert inv_line.price_unit == 100 , "price unit is not same"
assert inv_line.quantity == 5 , "product qty is not same"
diff --git a/addons/account_accountant/__openerp__.py b/addons/account_accountant/__openerp__.py
index 7c97112b875..ea4d7887a15 100644
--- a/addons/account_accountant/__openerp__.py
+++ b/addons/account_accountant/__openerp__.py
@@ -19,13 +19,13 @@
#
##############################################################################
{
- "name" : "Accounting and Finance",
- "version" : "1.1",
- "author" : "OpenERP SA",
- "category": 'Accounting & Finance',
- "sequence": 10,
- "summary": "Financial and Analytic Accounting",
- "description": """
+ 'name' : 'Accounting and Finance',
+ 'version' : '1.1',
+ 'author' : 'OpenERP SA',
+ 'category': 'Accounting & Finance',
+ 'sequence': 10,
+ 'summary': 'Financial and Analytic Accounting',
+ 'description': """
Accounting Access Rights.
=========================
@@ -36,13 +36,12 @@ It assigns manager and user access rights to the Administrator and only
user rights to Demo user.
""",
'website': 'http://www.openerp.com',
- 'init_xml': [],
- "depends" : ["account_voucher"],
- 'update_xml': [
+ 'depends' : ['account_voucher'],
+ 'data': [
'security/account_security.xml',
'account_accountant_data.xml'
],
- 'demo_xml': ['account_accountant_demo.xml'],
+ 'demo': ['account_accountant_demo.xml'],
'test': [],
'installable': True,
'auto_install': False,
diff --git a/addons/account_accountant/account_accountant_data.xml b/addons/account_accountant/account_accountant_data.xml
index 57fb229ad12..383b53cf49e 100644
--- a/addons/account_accountant/account_accountant_data.xml
+++ b/addons/account_accountant/account_accountant_data.xml
@@ -1,10 +1,10 @@
-
-
- Accounting
-
+
+
+
@@ -19,10 +19,8 @@
automatic100
-
-
-
+
diff --git a/addons/account_analytic_analysis/__openerp__.py b/addons/account_analytic_analysis/__openerp__.py
index e0bec840467..7a4e5d519bb 100644
--- a/addons/account_analytic_analysis/__openerp__.py
+++ b/addons/account_analytic_analysis/__openerp__.py
@@ -28,22 +28,19 @@
This module is for modifying account analytic view to show important data to project manager of services companies.
===================================================================================================================
-Adds menu to show relevant information to each manager.
-You can also view the report of account analytic summary
-user-wise as well as month-wise.
+Adds menu to show relevant information to each manager.You can also view the report of account analytic summary user-wise as well as month-wise.
""",
- "author": "Camptocamp",
- "website": "http://www.camptocamp.com/",
- "images": ["images/bill_tasks_works.jpeg","images/overpassed_accounts.jpeg"],
- "depends": ["hr_timesheet_invoice", "sale"], #although sale is technically not required to install this module, all menuitems are located under 'Sales' application
- "init_xml": [],
- "update_xml": [
- "security/ir.model.access.csv",
- "account_analytic_analysis_view.xml",
- "account_analytic_analysis_menu.xml",
- "account_analytic_analysis_cron.xml",
- ],
- 'demo_xml': [],
+ 'author': 'Camptocamp',
+ 'website': 'http://www.camptocamp.com/',
+ 'images': ['images/bill_tasks_works.jpeg','images/overpassed_accounts.jpeg'],
+ 'depends': ['hr_timesheet_invoice', 'sale'], #although sale is technically not required to install this module, all menuitems are located under 'Sales' application
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'account_analytic_analysis_view.xml',
+ 'account_analytic_analysis_menu.xml',
+ 'account_analytic_analysis_cron.xml',
+ ],
+ 'demo': [],
'installable': True,
'auto_install': False,
'certificate': '0042927202589',
diff --git a/addons/account_analytic_default/__openerp__.py b/addons/account_analytic_default/__openerp__.py
index 5ec1fc66c73..ef01867983a 100644
--- a/addons/account_analytic_default/__openerp__.py
+++ b/addons/account_analytic_default/__openerp__.py
@@ -20,12 +20,12 @@
##############################################################################
{
- 'name' : 'Account Analytic Defaults',
- 'version' : '1.0',
- "category": 'Accounting & Finance',
+ 'name': 'Account Analytic Defaults',
+ 'version': '1.0',
+ 'category': 'Accounting & Finance',
'description': """
Set default values for your analytic accounts.
-=============================================
+==============================================
Allows to automatically select analytic accounts based on criterions:
---------------------------------------------------------------------
@@ -35,17 +35,16 @@ Allows to automatically select analytic accounts based on criterions:
* Company
* Date
""",
- 'author' : 'OpenERP SA',
- 'website' : 'http://www.openerp.com',
- 'images' : ['images/analytic_defaults.jpeg'],
- 'depends' : ['sale'],
- 'init_xml' : [],
- 'update_xml': [
+ 'author': 'OpenERP SA',
+ 'website': 'http://www.openerp.com',
+ 'images': ['images/analytic_defaults.jpeg'],
+ 'depends': ['sale'],
+ 'data': [
'security/ir.model.access.csv',
'security/account_analytic_default_security.xml',
'account_analytic_default_view.xml'
],
- 'demo_xml' : [],
+ 'demo': [],
'installable': True,
'auto_install': False,
'certificate': '0074229833581',
diff --git a/addons/account_analytic_plans/__openerp__.py b/addons/account_analytic_plans/__openerp__.py
index a9000cfa490..675723f423a 100644
--- a/addons/account_analytic_plans/__openerp__.py
+++ b/addons/account_analytic_plans/__openerp__.py
@@ -26,45 +26,48 @@
'category': 'Accounting & Finance',
'description': """
This module allows to use several analytic plans according to the general journal.
-===================================================================================
+==================================================================================
Here multiple analytic lines are created when the invoice or the entries
are confirmed.
For example, you can define the following analytic structure:
- Projects
- Project 1
- SubProj 1.1
- SubProj 1.2
+-------------------------------------------------------------
+ * **Projects**
+ * Project 1
+ + SubProj 1.1
+
+ + SubProj 1.2
- Project 2
- Salesman
- Eric
- Fabien
+ * Project 2
+
+ * **Salesman**
+ * Eric
+
+ * Fabien
-Here, we have two plans: Projects and Salesman. An invoice line must
-be able to write analytic entries in the 2 plans: SubProj 1.1 and
-Fabien. The amount can also be split. The following example is for
-an invoice that touches the two subproject and assigned to one salesman:
+Here, we have two plans: Projects and Salesman. An invoice line must be able to write analytic entries in the 2 plans: SubProj 1.1 and Fabien. The amount can also be split.
+
+The following example is for an invoice that touches the two subprojects and assigned to one salesman:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+**Plan1:**
-Plan1:
- SubProject 1.1 : 50%
- SubProject 1.2 : 50%
-Plan2:
+ * SubProject 1.1 : 50%
+
+ * SubProject 1.2 : 50%
+
+**Plan2:**
Eric: 100%
-So when this line of invoice will be confirmed, it will generate 3 analytic lines,
-for one account entry.
+So when this line of invoice will be confirmed, it will generate 3 analytic lines,for one account entry.
-The analytic plan validates the minimum and maximum percentage at the time of creation
-of distribution models.
+The analytic plan validates the minimum and maximum percentage at the time of creation of distribution models.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/analytic_plan.jpeg'],
'depends': ['account', 'account_analytic_default'],
- 'init_xml': [],
- 'update_xml': [
+ 'data': [
'security/account_analytic_plan_security.xml',
'security/ir.model.access.csv',
'account_analytic_plans_view.xml',
@@ -72,7 +75,7 @@ of distribution models.
'wizard/analytic_plan_create_model_view.xml',
'wizard/account_crossovered_analytic_view.xml',
],
- 'demo_xml': [],
+ 'demo': [],
'test': ['test/acount_analytic_plans_report.yml'],
'installable': True,
'auto_install': False,
diff --git a/addons/account_anglo_saxon/__openerp__.py b/addons/account_anglo_saxon/__openerp__.py
index 5073d6522e3..330d0df0000 100644
--- a/addons/account_anglo_saxon/__openerp__.py
+++ b/addons/account_anglo_saxon/__openerp__.py
@@ -19,32 +19,34 @@
##############################################################################
{
- "name": "Anglo-Saxon Accounting",
- "version": "1.2",
- "author": "OpenERP SA, Veritos",
- "website": "http://openerp.com - http://veritos.nl",
- "description": """
+ 'name': 'Anglo-Saxon Accounting',
+ 'version': '1.2',
+ 'author': 'OpenERP SA, Veritos',
+ 'website': 'http://openerp.com - http://veritos.nl',
+ 'description': """
This module supports the Anglo-Saxon accounting methodology by changing the accounting logic with stock transactions.
=====================================================================================================================
-The difference between the Anglo-Saxon accounting countries and the Rhine (or also called
-Continental accounting) countries is the moment of taking the Cost of Goods Sold versus
-Cost of Sales. Anglo-Saxons accounting does take the cost when sales invoice is created,
-Continental accounting will take the cost at the moment the goods are shipped.
+The difference between the Anglo-Saxon accounting countries and the Rhine
+(or also called Continental accounting) countries is the moment of taking
+the Cost of Goods Sold versus Cost of Sales. Anglo-Saxons accounting does
+take the cost when sales invoice is created, Continental accounting will
+take the cost at the moment the goods are shipped.
-This module will add this functionality by using a interim account, to store the value of
-shipped goods and will contra book this interim account when the invoice is created to
-transfer this amount to the debtor or creditor account. Secondly, price differences between
-actual purchase price and fixed product standard price are booked on a separate account.""",
- "images": ["images/account_anglo_saxon.jpeg"],
- "depends": ["product", "purchase"],
- "category": "Accounting & Finance",
- "init_xml": [],
- "demo_xml": [],
- "update_xml": ["product_view.xml",],
- "auto_install": False,
- "installable": True,
- "certificate":"00557423080410733581",
+This module will add this functionality by using a interim account, to
+store the value of shipped goods and will contra book this interim
+account when the invoice is created to transfer this amount to the
+debtor or creditor account. Secondly, price differences between actual
+purchase price and fixed product standard price are booked on a separate
+account.""",
+ 'images': ['images/account_anglo_saxon.jpeg'],
+ 'depends': ['product', 'purchase'],
+ 'category': 'Accounting & Finance',
+ 'demo': [],
+ 'data': ['product_view.xml',],
+ 'auto_install': False,
+ 'installable': True,
+ 'certificate':'00557423080410733581',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_asset/__openerp__.py b/addons/account_asset/__openerp__.py
index 2cf79e9d30d..93bfd18a7cb 100644
--- a/addons/account_asset/__openerp__.py
+++ b/addons/account_asset/__openerp__.py
@@ -20,42 +20,40 @@
##############################################################################
{
- "name" : "Assets Management",
- "version" : "1.0",
- "depends" : ["account"],
- "author" : "OpenERP S.A.",
- "description": """
+ 'name': 'Assets Management',
+ 'version': '1.0',
+ 'depends': ['account'],
+ 'author': 'OpenERP S.A.',
+ 'description': """
Financial and accounting asset management.
==========================================
-This Module manages the assets owned by a company or an individual. It will keep track of depreciation's occurred on those assets.
-And it allows to create Move's of the depreciation lines.
+This Module manages the assets owned by a company or an individual. It will keep
+track of depreciation's occurred on those assets. And it allows to create Move's
+of the depreciation lines.
""",
- "website" : "http://www.openerp.com",
- "category" : "Accounting & Finance",
- "sequence": 32,
- "init_xml" : [
- ],
- "demo_xml" : [ 'account_asset_demo.xml'
- ],
+ 'website': 'http://www.openerp.com',
+ 'category': 'Accounting & Finance',
+ 'sequence': 32,
+ 'demo': [ 'account_asset_demo.xml'],
'test': [
'test/account_asset_demo.yml',
'test/account_asset.yml',
'test/account_asset_wizard.yml',
],
- "update_xml" : [
- "security/account_asset_security.xml",
- "security/ir.model.access.csv",
- "wizard/account_asset_change_duration_view.xml",
- "wizard/wizard_asset_compute_view.xml",
- "account_asset_view.xml",
- "account_asset_invoice_view.xml",
- "report/account_asset_report_view.xml",
+ 'data': [
+ 'security/account_asset_security.xml',
+ 'security/ir.model.access.csv',
+ 'wizard/account_asset_change_duration_view.xml',
+ 'wizard/wizard_asset_compute_view.xml',
+ 'account_asset_view.xml',
+ 'account_asset_invoice_view.xml',
+ 'report/account_asset_report_view.xml',
],
- "auto_install": False,
- "installable": True,
- "application": False,
+ 'auto_install': False,
+ 'installable': True,
+ 'application': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_bank_statement_extensions/__openerp__.py b/addons/account_bank_statement_extensions/__openerp__.py
index a3765d33622..7571eef3e54 100644
--- a/addons/account_bank_statement_extensions/__openerp__.py
+++ b/addons/account_bank_statement_extensions/__openerp__.py
@@ -30,19 +30,20 @@ Module that extends the standard account_bank_statement_line object for improved
===================================================================================================
This module adds:
+-----------------
- valuta date
- batch payments
- traceability of changes to bank statement lines
- bank statement line views
- bank statements balances report
- - performance improvements for digital import of bank statement (via 'ebanking_import' context flag)
- - name_search on res.partner.bank enhanced to allow search on bank and iban account numbers
+ - performance improvements for digital import of bank statement (via
+ 'ebanking_import' context flag)
+ - name_search on res.partner.bank enhanced to allow search on bank
+ and iban account numbers
''',
'depends': ['account'],
- 'demo_xml': [],
- 'init_xml': [
- ],
- 'update_xml' : [
+ 'demo': [],
+ 'data' : [
'security/ir.model.access.csv',
'account_bank_statement_view.xml',
'account_bank_statement_report.xml',
diff --git a/addons/account_budget/__openerp__.py b/addons/account_budget/__openerp__.py
index e03c66c2132..928e87f1202 100644
--- a/addons/account_budget/__openerp__.py
+++ b/addons/account_budget/__openerp__.py
@@ -32,23 +32,26 @@ Once the Budgets are defined (in Invoicing/Budgets/Budgets), the Project Manager
can set the planned amount on each Analytic Account.
The accountant has the possibility to see the total of amount planned for each
-Budget in order to ensure the total planned is not greater/lower than what he planned
-for this Budget. Each list of record can also be switched to a graphical view of it.
+Budget in order to ensure the total planned is not greater/lower than what he
+planned for this Budget. Each list of record can also be switched to a graphical
+view of it.
Three reports are available:
+----------------------------
+ 1. The first is available from a list of Budgets. It gives the spreading, for
+ these Budgets, of the Analytic Accounts.
- 1. The first is available from a list of Budgets. It gives the spreading, for these Budgets, of the Analytic Accounts.
+ 2. The second is a summary of the previous one, it only gives the spreading,
+ for the selected Budgets, of the Analytic Accounts.
- 2. The second is a summary of the previous one, it only gives the spreading, for the selected Budgets, of the Analytic Accounts.
-
- 3. The last one is available from the Analytic Chart of Accounts. It gives the spreading, for the selected Analytic Accounts of Budgets.
+ 3. The last one is available from the Analytic Chart of Accounts. It gives
+ the spreading, for the selected Analytic Accounts of Budgets.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/budget.jpeg','images/budgetary_position.jpeg'],
'depends': ['account'],
- 'init_xml': [],
- 'update_xml': [
+ 'data': [
'security/ir.model.access.csv',
'security/account_budget_security.xml',
'account_budget_view.xml',
@@ -59,11 +62,11 @@ Three reports are available:
'wizard/account_budget_crossovered_summary_report_view.xml',
'wizard/account_budget_crossovered_report_view.xml',
],
- 'demo_xml': ['account_budget_demo.xml'],
+ 'demo': ['account_budget_demo.xml'],
'test':[
- 'test/account_budget.yml',
- 'test/account_budget_report.yml',
- ],
+ 'test/account_budget.yml',
+ 'test/account_budget_report.yml',
+ ],
'installable': True,
'auto_install': False,
'certificate': '0043819694157',
diff --git a/addons/account_cancel/__openerp__.py b/addons/account_cancel/__openerp__.py
index c616cc9d5e0..b1feedda55a 100644
--- a/addons/account_cancel/__openerp__.py
+++ b/addons/account_cancel/__openerp__.py
@@ -20,26 +20,25 @@
##############################################################################
{
- "name" : "Cancel Journal Entries",
- "version" : "1.1",
- "author" : "OpenERP SA",
- "category": 'Accounting & Finance',
- "description": """
-Allows cancelling accounting entries.
-=====================================
+ 'name': 'Cancel Journal Entries',
+ 'version': '1.1',
+ 'author': 'OpenERP SA',
+ 'category': 'Accounting & Finance',
+ 'description': """
+Allows canceling accounting entries.
+====================================
-This module adds 'Allow Cancelling Entries' field on form view of account journal.
+This module adds 'Allow Canceling Entries' field on form view of account journal.
If set to true it allows user to cancel entries & invoices.
""",
'website': 'http://www.openerp.com',
- "images" : ["images/account_cancel.jpeg"],
- "depends" : ["account"],
- 'init_xml': [],
- 'update_xml': ['account_cancel_view.xml' ],
- 'demo_xml': [],
+ 'images': ['images/account_cancel.jpeg'],
+ 'depends' : ['account'],
+ 'data': ['account_cancel_view.xml' ],
+ 'demo': [],
'installable': True,
'auto_install': False,
- "certificate" : "001101250473177981989",
+ 'certificate': '001101250473177981989',
}
diff --git a/addons/account_chart/__openerp__.py b/addons/account_chart/__openerp__.py
index c0c9245a477..2c458ceb49f 100644
--- a/addons/account_chart/__openerp__.py
+++ b/addons/account_chart/__openerp__.py
@@ -23,7 +23,7 @@
{
'name': 'Template of Charts of Accounts',
'version': '1.1',
- "category": 'Hidden/Dependency',
+ 'category': 'Hidden/Dependency',
'description': """
Remove minimal account chart.
=============================
@@ -33,9 +33,8 @@ Deactivates minimal chart of accounts.
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['account'],
- 'init_xml': [],
- 'update_xml': [],
- 'demo_xml': [],
+ 'data': [],
+ 'demo': [],
'installable': True,
'certificate': '0073332443901',
}
diff --git a/addons/account_check_writing/__openerp__.py b/addons/account_check_writing/__openerp__.py
index faba82928ed..f5a2355b002 100644
--- a/addons/account_check_writing/__openerp__.py
+++ b/addons/account_check_writing/__openerp__.py
@@ -19,29 +19,24 @@
#
##############################################################################
{
- "name" : "Check Writing",
- "version" : "1.1",
- "author" : "OpenERP SA, NovaPoint Group",
- "category": "Generic Modules/Accounting",
- "description": """
+ 'name': 'Check Writing',
+ 'version': '1.1',
+ 'author': 'OpenERP SA, NovaPoint Group',
+ 'category': 'Generic Modules/Accounting',
+ 'description': """
Module for the Check Writing and Check Printing.
+================================================
""",
'website': 'http://www.openerp.com',
- 'init_xml': [],
- "depends" : [
- "account_voucher",
- ],
- 'update_xml': [
+ 'depends' : ['account_voucher'],
+ 'data': [
'account_check_writing_report.xml',
'account_view.xml',
'account_voucher_view.xml',
'account_check_writing_data.xml',
],
- 'demo_xml': [
- 'account_demo.xml',
- ],
- 'test': [
- ],
+ 'demo': ['account_demo.xml'],
+ 'test': [],
'installable': True,
'active': False,
}
diff --git a/addons/account_coda/__openerp__.py b/addons/account_coda/__openerp__.py
index 094ab45acc0..03527eaa2ec 100644
--- a/addons/account_coda/__openerp__.py
+++ b/addons/account_coda/__openerp__.py
@@ -20,74 +20,92 @@
#
##############################################################################
{
- "name": 'Belgium - Import Bank CODA Statements',
- "version": '2.1',
- "author": 'Noviat',
- "category": 'Accounting & Finance',
- "description": '''
+ 'name': 'Belgium - Import Bank CODA Statements',
+ 'version': '2.1',
+ 'author': 'Noviat',
+ 'category': 'Accounting & Finance',
+ 'description': '''
Module to import CODA bank statements.
======================================
Supported are CODA flat files in V2 format from Belgian bank accounts.
+----------------------------------------------------------------------
* CODA v1 support.
* CODA v2.2 support.
* Foreign Currency support.
* Support for all data record types (0, 1, 2, 3, 4, 8, 9).
- * Parsing & logging of all Transaction Codes and Structured Format Communications.
+ * Parsing & logging of all Transaction Codes and Structured Format
+ Communications.
* Automatic Financial Journal assignment via CODA configuration parameters.
* Support for multiple Journals per Bank Account Number.
- * Support for multiple statements from different bank accounts in a single CODA file.
- * Support for 'parsing only' CODA Bank Accounts (defined as type='info' in the CODA Bank Account configuration records).
- * Multi-language CODA parsing, parsing configuration data provided for EN, NL, FR.
+ * Support for multiple statements from different bank accounts in a single
+ CODA file.
+ * Support for 'parsing only' CODA Bank Accounts (defined as type='info' in
+ the CODA Bank Account configuration records).
+ * Multi-language CODA parsing, parsing configuration data provided for EN,
+ NL, FR.
The machine readable CODA Files are parsed and stored in human readable format in
CODA Bank Statements. Also Bank Statements are generated containing a subset of
-the CODA information (only those transaction lines that are required for the creation
-of the Financial Accounting records). The CODA Bank Statement is a 'read-only'
-object, hence remaining a reliable representation of the original CODA file whereas
-the Bank Statement will get modified as required by accounting business processes.
+the CODA information (only those transaction lines that are required for the
+creation of the Financial Accounting records). The CODA Bank Statement is a
+'read-only' object, hence remaining a reliable representation of the original
+CODA file whereas the Bank Statement will get modified as required by accounting
+business processes.
CODA Bank Accounts configured as type 'Info' will only generate CODA Bank Statements.
-A removal of one object in the CODA processing results in the removal of the associated
-objects. The removal of a CODA File containing multiple Bank Statements will also
-remove those associated statements.
+A removal of one object in the CODA processing results in the removal of the
+associated objects. The removal of a CODA File containing multiple Bank
+Statements will also remove those associated statements.
The following reconciliation logic has been implemented in the CODA processing:
-
-1) The Company's Bank Account Number of the CODA statement is compared against the Bank Account Number field of the Company's CODA Bank Account configuration records (whereby bank accounts defined in type='info' configuration records are ignored). If this is the case an 'internal transfer' transaction is generated using the 'Internal Transfer Account' field of the CODA File Import wizard.
-2) As a second step the 'Structured Communication' field of the CODA transaction line is matched against the reference field of in- and outgoing invoices (supported : Belgian Structured Communication Type).
-3) When the previous step doesn't find a match, the transaction counterparty is located via the Bank Account Number configured on the OpenERP Customer and Supplier records.
-4) In case the previous steps are not successful, the transaction is generated by using the 'Default Account for Unrecognized Movement' field of the CODA File Import wizard in order to allow further manual processing.
+-------------------------------------------------------------------------------
+ 1) The Company's Bank Account Number of the CODA statement is compared against
+ the Bank Account Number field of the Company's CODA Bank Account
+ configuration records (whereby bank accounts defined in type='info'
+ configuration records are ignored). If this is the case an 'internal transfer'
+ transaction is generated using the 'Internal Transfer Account' field of the
+ CODA File Import wizard.
+ 2) As a second step the 'Structured Communication' field of the CODA transaction
+ line is matched against the reference field of in- and outgoing invoices
+ (supported : Belgian Structured Communication Type).
+ 3) When the previous step doesn't find a match, the transaction counterparty is
+ located via the Bank Account Number configured on the OpenERP Customer and
+ Supplier records.
+ 4) In case the previous steps are not successful, the transaction is generated
+ by using the 'Default Account for Unrecognized Movement' field of the CODA
+ File Import wizard in order to allow further manual processing.
In stead of a manual adjustment of the generated Bank Statements, you can also
re-import the CODA after updating the OpenERP database with the information that
was missing to allow automatic reconciliation.
Remark on CODA V1 support:
-In some cases a transaction code, transaction category or structured communication code has been given a new or clearer description in CODA V2.
-The description provided by the CODA configuration tables is based upon the CODA V2.2 specifications.
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+In some cases a transaction code, transaction category or structured
+communication code has been given a new or clearer description in CODA V2.The
+description provided by the CODA configuration tables is based upon the CODA
+V2.2 specifications.
If required, you can manually adjust the descriptions via the CODA configuration menu.
''',
- "images" : ["images/coda_logs.jpeg","images/import_coda_logs.jpeg"],
- "depends": ['account_voucher','base_iban', 'l10n_be_invoice_bba', 'account_bank_statement_extensions'],
- "demo_xml": [],
- "init_xml": [
+ 'images' : ['images/coda_logs.jpeg','images/import_coda_logs.jpeg'],
+ 'depends': ['account_voucher','base_iban', 'l10n_be_invoice_bba', 'account_bank_statement_extensions'],
+ 'demo': [],
+ 'data': [
'account_coda_trans_type.xml',
'account_coda_trans_code.xml',
'account_coda_trans_category.xml',
'account_coda_comm_type.xml',
- ],
- "update_xml" : [
'security/ir.model.access.csv',
'security/account_security.xml',
'account_coda_wizard.xml',
'account_coda_view.xml',
],
- "auto_install": False,
- "installable": True,
- "license": 'AGPL-3',
- "certificate" : "001237207321716002029",
+ 'auto_install': False,
+ 'installable': True,
+ 'license': 'AGPL-3',
+ 'certificate' : '001237207321716002029',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_followup/__openerp__.py b/addons/account_followup/__openerp__.py
index e8724b01b4e..43b1c4b2859 100644
--- a/addons/account_followup/__openerp__.py
+++ b/addons/account_followup/__openerp__.py
@@ -28,30 +28,27 @@ Module to automate letters for unpaid invoices, with multi-level recalls.
==========================================================================
You can define your multiple levels of recall through the menu:
+---------------------------------------------------------------
+ **Invoicing** / **Configuration** / **Miscellaneous** / **Follow-ups**
- Invoicing/Configuration/Miscellaneous/Follow-ups
-
-Once it is defined, you can automatically print recalls every day through simply
-clicking on the menu:
-
- Invoicing/Periodical Processing/Billing/Send follow-ups
+Once it is defined, you can automatically print recalls every day through simply clicking on the menu:
+------------------------------------------------------------------------------------------------------
+ **Invoicing** / **Periodical Processing** / **Billing** / **Send follow-ups**
It will generate a PDF with all the letters according to the the different levels
of recall defined. You can define different policies for different companies. You
can also send mail to the customer.
-Note that if you want to check the follow-up level for a given partner/account
-entry, you can do from in the menu:
-
- Invoicing/Reporting/Generic Reporting/Partners/Follow-ups Sent
+Note that if you want to check the follow-up level for a given partner/account entry, you can do from in the menu:
+------------------------------------------------------------------------------------------------------------------
+ **Invoicing** / **Reporting** / **Generic Reporting** / **Partners** / **Follow-ups Sent**
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/follow_ups.jpeg','images/send_followups.jpeg'],
'depends': ['account_accountant', 'mail'],
- 'init_xml': [],
- 'update_xml': [
+ 'data': [
'security/account_followup_security.xml',
'security/ir.model.access.csv',
'wizard/account_followup_print_view.xml',
@@ -60,7 +57,7 @@ entry, you can do from in the menu:
'account_followup_view.xml',
'account_followup_data.xml',
],
- 'demo_xml': [],
+ 'demo': [],
'test': [
'test/account_followup.yml',
'test/account_followup_report.yml',
diff --git a/addons/account_payment/__openerp__.py b/addons/account_payment/__openerp__.py
index 9e7209a348e..dd08386b064 100644
--- a/addons/account_payment/__openerp__.py
+++ b/addons/account_payment/__openerp__.py
@@ -20,26 +20,31 @@
##############################################################################
{
- "name": "Suppliers Payment Management",
- "version": "1.1",
- "author": "OpenERP SA",
- "category": "Accounting & Finance",
- "description": """
+ 'name': 'Suppliers Payment Management',
+ 'version': '1.1',
+ 'author': 'OpenERP SA',
+ 'category': 'Accounting & Finance',
+ 'description': """
Module to manage the payment of your supplier invoices.
=======================================================
-This module allows you to create and manage your payment orders, with purposes to
+This module allows you to create and manage your payment orders, with purposes to
+---------------------------------------------------------------------------------
* serve as base for an easy plug-in of various automated payment mechanisms.
* provide a more efficient way to manage invoice payment.
Warning:
---------
-The confirmation of a payment order does _not_ create accounting entries, it just records the fact that you gave your payment order to your bank. The booking of your order must be encoded as usual through a bank statement. Indeed, it's only when you get the confirmation from your bank that your order has been accepted that you can book it in your accounting. To help you with that operation, you have a new option to import payment orders as bank statement lines.
+~~~~~~~~
+The confirmation of a payment order does _not_ create accounting entries, it just
+records the fact that you gave your payment order to your bank. The booking of
+your order must be encoded as usual through a bank statement. Indeed, it's only
+when you get the confirmation from your bank that your order has been accepted
+that you can book it in your accounting. To help you with that operation, you
+have a new option to import payment orders as bank statement lines.
""",
'images': ['images/payment_mode.jpeg','images/payment_order.jpeg'],
'depends': ['account','account_voucher'],
- 'init_xml': [],
- 'update_xml': [
+ 'data': [
'security/account_payment_security.xml',
'security/ir.model.access.csv',
'wizard/account_payment_pay_view.xml',
@@ -51,7 +56,7 @@ The confirmation of a payment order does _not_ create accounting entries, it jus
'account_invoice_view.xml',
'account_payment_report.xml',
],
- 'demo_xml': ['account_payment_demo.xml'],
+ 'demo': ['account_payment_demo.xml'],
'test': [
'test/account_payment_demo.yml',
'test/cancel_payment_order.yml',
diff --git a/addons/account_sequence/__openerp__.py b/addons/account_sequence/__openerp__.py
index 0f860d400e9..16f13dc5178 100644
--- a/addons/account_sequence/__openerp__.py
+++ b/addons/account_sequence/__openerp__.py
@@ -22,7 +22,7 @@
{
'name': 'Entries Sequence Numbering',
'version': '1.1',
- "category": 'Accounting & Finance',
+ 'category': 'Accounting & Finance',
'description': """
This module maintains internal sequence number for accounting entries.
======================================================================
@@ -30,6 +30,7 @@ This module maintains internal sequence number for accounting entries.
Allows you to configure the accounting sequences to be maintained.
You can customize the following attributes of the sequence:
+-----------------------------------------------------------
* Prefix
* Suffix
* Next Number
@@ -40,13 +41,12 @@ You can customize the following attributes of the sequence:
'website': 'http://www.openerp.com',
'images': ['images/internal_sequence_number.jpeg'],
'depends': ['account'],
- 'init_xml': [],
- 'update_xml': [
+ 'data': [
'account_sequence_data.xml',
'account_sequence_installer_view.xml',
'account_sequence.xml'
],
- 'demo_xml': [],
+ 'demo': [],
'installable': True,
'auto_install': False,
'certificate': '00475376442024623469',
diff --git a/addons/account_voucher/__openerp__.py b/addons/account_voucher/__openerp__.py
index 16ea771d123..03befcc3acd 100644
--- a/addons/account_voucher/__openerp__.py
+++ b/addons/account_voucher/__openerp__.py
@@ -20,12 +20,12 @@
##############################################################################
{
- "name" : "eInvoicing & Payments",
- "version" : "1.0",
- "author" : 'OpenERP SA',
- "summary": 'Send Invoices and Track Payments',
- "description": """
-eInvoicing & Payments module manage all Voucher Entries such as "Reconciliation Entries", "Adjustment Entries", "Closing or Opening Entries" for Sales, Purchase, Bank, Cash, Expense, Contra.
+ 'name' : 'eInvoicing & Payments',
+ 'version' : '1.0',
+ 'author' : 'OpenERP SA',
+ 'summary': 'Send Invoices and Track Payments',
+ 'description': """
+eInvoicing & Payments module manage all Voucher Entries such as 'Reconciliation Entries', 'Adjustment Entries', 'Closing or Opening Entries' for Sales, Purchase, Bank, Cash, Expense, Contra.
==============================================================================================================================================================================================
* Voucher Entry
@@ -33,47 +33,43 @@ eInvoicing & Payments module manage all Voucher Entries such as "Reconciliation
* Voucher Payment [Customer & Supplier]
* Cheque Register
""",
- "category": 'Accounting & Finance',
- "sequence": 4,
- "website" : "http://openerp.com",
- "images" : ["images/customer_payment.jpeg","images/journal_voucher.jpeg","images/sales_receipt.jpeg","images/supplier_voucher.jpeg"],
- "depends" : ["account"],
- "init_xml" : [
+ 'category': 'Accounting & Finance',
+ 'sequence': 4,
+ 'website' : 'http://openerp.com',
+ 'images' : ['images/customer_payment.jpeg','images/journal_voucher.jpeg','images/sales_receipt.jpeg','images/supplier_voucher.jpeg'],
+ 'depends' : ['account'],
+ 'demo' : [],
+ 'data' : [
+ 'security/ir.model.access.csv',
+ 'account_voucher_sequence.xml',
+ 'account_voucher_workflow.xml',
+ 'account_voucher_report.xml',
+ 'wizard/account_statement_from_invoice_view.xml',
+ 'account_voucher_view.xml',
+ 'voucher_payment_receipt_view.xml',
+ 'voucher_sales_purchase_view.xml',
+ 'account_voucher_wizard.xml',
+ 'account_voucher_pay_invoice.xml',
+ 'report/account_voucher_sales_receipt_view.xml',
+ 'security/account_voucher_security.xml',
'account_voucher_data.xml',
],
-
- "demo_xml" : [],
-
- "update_xml" : [
- "security/ir.model.access.csv",
- "account_voucher_sequence.xml",
- "account_voucher_workflow.xml",
- "account_voucher_report.xml",
- "wizard/account_statement_from_invoice_view.xml",
- "account_voucher_view.xml",
- "voucher_payment_receipt_view.xml",
- "voucher_sales_purchase_view.xml",
- "account_voucher_wizard.xml",
- "account_voucher_pay_invoice.xml",
- "report/account_voucher_sales_receipt_view.xml",
- "security/account_voucher_security.xml"
- ],
- "test" : [
- "test/account_voucher.yml",
- "test/sales_receipt.yml",
- "test/sales_payment.yml",
- "test/account_voucher_report.yml",
- "test/case1_usd_usd.yml",
- "test/case2_usd_eur_debtor_in_eur.yml",
- "test/case2_usd_eur_debtor_in_usd.yml",
- "test/case3_eur_eur.yml",
- "test/case4_cad_chf.yml",
- "test/case_eur_usd.yml",
+ 'test' : [
+ 'test/account_voucher.yml',
+ 'test/sales_receipt.yml',
+ 'test/sales_payment.yml',
+ 'test/account_voucher_report.yml',
+ 'test/case1_usd_usd.yml',
+ 'test/case2_usd_eur_debtor_in_eur.yml',
+ 'test/case2_usd_eur_debtor_in_usd.yml',
+ 'test/case3_eur_eur.yml',
+ 'test/case4_cad_chf.yml',
+ 'test/case_eur_usd.yml',
],
'certificate': '0037580727101',
- "auto_install": False,
- "application": True,
- "installable": True,
+ 'auto_install': False,
+ 'application': True,
+ 'installable': True,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/account_voucher/test/account_voucher.yml b/addons/account_voucher/test/account_voucher.yml
index 9cfb9d61229..2ea6c0b7890 100644
--- a/addons/account_voucher/test/account_voucher.yml
+++ b/addons/account_voucher/test/account_voucher.yml
@@ -54,7 +54,7 @@
currency_id: base.EUR
journal_id: account.bank_journal
name: Voucher Axelor
- narration: Basic PC
+ narration: PC Assemble SC234
line_dr_ids:
- account_id: account.cash
amount: 1000.0
diff --git a/addons/account_voucher/test/account_voucher_report.yml b/addons/account_voucher/test/account_voucher_report.yml
index 85432f747aa..3996e0c27e4 100644
--- a/addons/account_voucher/test/account_voucher_report.yml
+++ b/addons/account_voucher/test/account_voucher_report.yml
@@ -6,7 +6,7 @@
company_id: base.main_company
journal_id: account.bank_journal
name: Voucher Axelor
- narration: Basic PC
+ narration: PC Assemble SC234
amount: 1000.0
line_ids:
- account_id: account.cash
diff --git a/addons/account_voucher/test/case1_usd_usd.yml b/addons/account_voucher/test/case1_usd_usd.yml
index 45c352955f4..b706e6f0644 100644
--- a/addons/account_voucher/test/case1_usd_usd.yml
+++ b/addons/account_voucher/test/case1_usd_usd.yml
@@ -79,7 +79,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@@ -112,7 +112,7 @@
period_id: account.period_2
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/case2_suppl_usd_eur.yml b/addons/account_voucher/test/case2_suppl_usd_eur.yml
index cc4c238aaaf..1a0c250395e 100644
--- a/addons/account_voucher/test/case2_suppl_usd_eur.yml
+++ b/addons/account_voucher/test/case2_suppl_usd_eur.yml
@@ -50,7 +50,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_expense
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@@ -85,7 +85,7 @@
type : in_invoice
invoice_line:
- account_id: account.a_expense
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/case2_usd_eur_debtor_in_eur.yml b/addons/account_voucher/test/case2_usd_eur_debtor_in_eur.yml
index c37783247db..91f8a629cbb 100644
--- a/addons/account_voucher/test/case2_usd_eur_debtor_in_eur.yml
+++ b/addons/account_voucher/test/case2_usd_eur_debtor_in_eur.yml
@@ -85,7 +85,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@@ -118,7 +118,7 @@
period_id: account.period_2
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/case2_usd_eur_debtor_in_usd.yml b/addons/account_voucher/test/case2_usd_eur_debtor_in_usd.yml
index 36593a2b9cd..cc5e819af30 100644
--- a/addons/account_voucher/test/case2_usd_eur_debtor_in_usd.yml
+++ b/addons/account_voucher/test/case2_usd_eur_debtor_in_usd.yml
@@ -85,7 +85,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@@ -118,7 +118,7 @@
period_id: account.period_2
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/case3_eur_eur.yml b/addons/account_voucher/test/case3_eur_eur.yml
index d70bd41a2ff..37ea7ea981a 100644
--- a/addons/account_voucher/test/case3_eur_eur.yml
+++ b/addons/account_voucher/test/case3_eur_eur.yml
@@ -39,7 +39,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 150.0
quantity: 1.0
product_id: product.product_product_3
@@ -72,7 +72,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 80.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/case4_cad_chf.yml b/addons/account_voucher/test/case4_cad_chf.yml
index 5ca901cdbca..a78669c95ef 100644
--- a/addons/account_voucher/test/case4_cad_chf.yml
+++ b/addons/account_voucher/test/case4_cad_chf.yml
@@ -73,7 +73,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/case_eur_usd.yml b/addons/account_voucher/test/case_eur_usd.yml
index 2ca2ded1a72..125277191cb 100644
--- a/addons/account_voucher/test/case_eur_usd.yml
+++ b/addons/account_voucher/test/case_eur_usd.yml
@@ -52,7 +52,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 1000.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/account_voucher/test/sales_payment.yml b/addons/account_voucher/test/sales_payment.yml
index 585ce23aace..88f10e55918 100644
--- a/addons/account_voucher/test/sales_payment.yml
+++ b/addons/account_voucher/test/sales_payment.yml
@@ -7,7 +7,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
- name: '[PC1] Basic PC'
+ name: '[PCSC234] PC Assemble SC234'
price_unit: 450.0
quantity: 1.0
product_id: product.product_product_3
diff --git a/addons/analytic/__openerp__.py b/addons/analytic/__openerp__.py
index 859a65e7856..10762a1d2be 100644
--- a/addons/analytic/__openerp__.py
+++ b/addons/analytic/__openerp__.py
@@ -20,13 +20,13 @@
##############################################################################
{
- "name" : "Analytic Accounting",
- "version": "1.1",
- "author" : "OpenERP SA",
- "website" : "http://www.openerp.com",
- "category": 'Hidden/Dependency',
- "depends" : ["base", "decimal_precision", "mail"],
- "description": """
+ 'name' : 'Analytic Accounting',
+ 'version': '1.1',
+ 'author' : 'OpenERP SA',
+ 'website' : 'http://www.openerp.com',
+ 'category': 'Hidden/Dependency',
+ 'depends' : ['base', 'decimal_precision', 'mail'],
+ 'description': """
Module for defining analytic accounting object.
===============================================
@@ -34,16 +34,15 @@ In OpenERP, analytic accounts are linked to general accounts but are treated
totally independently. So, you can enter various different analytic operations
that have no counterpart in the general financial accounts.
""",
- "init_xml" : [],
- "update_xml": ['security/analytic_security.xml',
- "security/ir.model.access.csv",
- "analytic_sequence.xml",
- "analytic_view.xml"
- ],
- 'demo_xml': [
+ 'data': [
+ 'security/analytic_security.xml',
+ 'security/ir.model.access.csv',
+ 'analytic_sequence.xml',
+ 'analytic_view.xml'
],
+ 'demo': [],
'installable': True,
'auto_install': False,
- 'certificate' : "00462253285027988541",
+ 'certificate' : '00462253285027988541',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/analytic_contract_expense_project/__openerp__.py b/addons/analytic_contract_expense_project/__openerp__.py
index b569330d117..8996e9f7565 100644
--- a/addons/analytic_contract_expense_project/__openerp__.py
+++ b/addons/analytic_contract_expense_project/__openerp__.py
@@ -26,18 +26,14 @@
'category': 'Hidden',
'description': """
This module is for modifying project view to show some data related to the hr_expense module.
-
+=============================================================================================
""",
- "author": "OpenERP S.A.",
- "website": "http://www.openerp.com/",
- "depends": ["analytic_contract_hr_expense","project"],
- "init_xml": [],
- "update_xml": [
- "analytic_contract_expense_project_view.xml",
- ],
- 'demo_xml': [],
- "css" : [
- ],
+ 'author': 'OpenERP S.A.',
+ 'website': 'http://www.openerp.com/',
+ 'depends': ['analytic_contract_hr_expense','project'],
+ 'data': ['analytic_contract_expense_project_view.xml'],
+ 'demo': [],
+ 'css' : [],
'installable': True,
'auto_install': True,
}
diff --git a/addons/analytic_contract_hr_expense/__openerp__.py b/addons/analytic_contract_hr_expense/__openerp__.py
index 6d5cca8b574..8e807923550 100644
--- a/addons/analytic_contract_hr_expense/__openerp__.py
+++ b/addons/analytic_contract_hr_expense/__openerp__.py
@@ -26,18 +26,14 @@
'category': 'Hidden',
'description': """
This module is for modifying account analytic view to show some data related to the hr_expense module.
-
+======================================================================================================
""",
- "author": "OpenERP S.A.",
- "website": "http://www.openerp.com/",
- "depends": ["hr_expense","account_analytic_analysis"],
- "init_xml": [],
- "update_xml": [
- "analytic_contract_hr_expense_view.xml",
- ],
- 'demo_xml': [],
- "css" : [
- ],
+ 'author': 'OpenERP S.A.',
+ 'website': 'http://www.openerp.com/',
+ 'depends': ['hr_expense','account_analytic_analysis'],
+ 'data': ['analytic_contract_hr_expense_view.xml'],
+ 'demo': [],
+ 'css' : [],
'installable': True,
'auto_install': True,
}
diff --git a/addons/analytic_contract_project/__openerp__.py b/addons/analytic_contract_project/__openerp__.py
index 4e4f621663c..9c0be102760 100644
--- a/addons/analytic_contract_project/__openerp__.py
+++ b/addons/analytic_contract_project/__openerp__.py
@@ -20,18 +20,18 @@
##############################################################################
{
- "name" : "Contract on Project",
- "version": "1.1",
- "author" : "OpenERP SA",
+ 'name' : 'Contract on Project',
+ 'version': '1.1',
+ 'author' : 'OpenERP SA',
'category': 'Hidden',
- "website" : "http://www.openerp.com",
- "depends" : ["project", "account_analytic_analysis"],
- "description": """
+ 'website' : 'http://www.openerp.com',
+ 'depends' : ['project', 'account_analytic_analysis'],
+ 'description': """
Add "Contract Data" in project view.
+====================================
""",
- "init_xml" : [],
- "update_xml": ["analytic_contract_project_view.xml"],
- 'demo_xml': [],
+ 'data': ['analytic_contract_project_view.xml'],
+ 'demo': [],
'installable': True,
'auto_install': True,
}
diff --git a/addons/analytic_user_function/__openerp__.py b/addons/analytic_user_function/__openerp__.py
index 1c1f345aca0..f23296012d2 100644
--- a/addons/analytic_user_function/__openerp__.py
+++ b/addons/analytic_user_function/__openerp__.py
@@ -41,9 +41,8 @@ compatible with older configurations.
'website': 'http://www.openerp.com',
'images': ['images/analytic_user_function.jpeg'],
'depends': ['hr_timesheet_sheet'],
- 'init_xml': [],
- 'update_xml': ['analytic_user_function_view.xml', 'security/ir.model.access.csv'],
- 'demo_xml': [],
+ 'data': ['analytic_user_function_view.xml', 'security/ir.model.access.csv'],
+ 'demo': [],
'installable': True,
'auto_install': False,
'certificate': '0082277138269',
diff --git a/addons/anonymization/__openerp__.py b/addons/anonymization/__openerp__.py
index acfc5ac3792..865fb19ea07 100644
--- a/addons/anonymization/__openerp__.py
+++ b/addons/anonymization/__openerp__.py
@@ -40,11 +40,7 @@ anonymization process to recover your previous data.
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
- 'init_xml': [],
- 'update_xml': [],
- 'demo_xml': [
- 'anonymization_demo.xml',
- ],
+ 'demo': ['anonymization_demo.xml'],
'data': [
'ir.model.fields.anonymization.csv',
'security/ir.model.access.csv',
diff --git a/addons/anonymization/anonymization_view.xml b/addons/anonymization/anonymization_view.xml
index 5aba288d4f8..1a6c6c355d5 100644
--- a/addons/anonymization/anonymization_view.xml
+++ b/addons/anonymization/anonymization_view.xml
@@ -76,7 +76,7 @@
-
+
Anonymize Database
@@ -163,4 +163,3 @@
-
diff --git a/addons/association/__openerp__.py b/addons/association/__openerp__.py
index 2b7fcab8b12..665968c6613 100644
--- a/addons/association/__openerp__.py
+++ b/addons/association/__openerp__.py
@@ -33,8 +33,8 @@ membership products (schemes).
""",
'author': 'OpenERP SA',
'depends': ['base_setup', 'membership', 'event'],
- 'update_xml': ['security/ir.model.access.csv', 'profile_association.xml'],
- 'demo_xml': [],
+ 'data': ['security/ir.model.access.csv', 'profile_association.xml'],
+ 'demo': [],
'installable': True,
'auto_install': False,
'certificate': '0078696047261',
diff --git a/addons/audittrail/__openerp__.py b/addons/audittrail/__openerp__.py
index 9ba403bcee3..3ba8c59a2f3 100644
--- a/addons/audittrail/__openerp__.py
+++ b/addons/audittrail/__openerp__.py
@@ -34,13 +34,12 @@ and can check logs.
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base'],
- 'init_xml': [],
- 'update_xml': [
+ 'data': [
'wizard/audittrail_view_log_view.xml',
'audittrail_view.xml',
'security/ir.model.access.csv',
],
- 'demo_xml': ['audittrail_demo.xml'],
+ 'demo': ['audittrail_demo.xml'],
'installable': True,
'auto_install': False,
'certificate': '0062572348749',
diff --git a/addons/auth_anonymous/__openerp__.py b/addons/auth_anonymous/__openerp__.py
index 95d98e65fea..1246850fe04 100644
--- a/addons/auth_anonymous/__openerp__.py
+++ b/addons/auth_anonymous/__openerp__.py
@@ -1,19 +1,16 @@
{
- 'name': 'Anonymous',
- 'description': 'Allow anonymous access to OpenERP.',
- 'author': 'OpenERP SA',
- 'version': '1.0',
- 'category': 'Authentication',
- 'website': 'http://www.openerp.com',
- 'installable': True,
- 'depends': ['web'],
- 'data': [
- 'auth_anonymous.xml',
- ],
- 'js': [
- 'static/src/js/auth_anonymous.js',
- ],
- 'qweb': [
- 'static/src/xml/auth_anonymous.xml',
- ],
+ 'name': 'Anonymous',
+ 'description': """
+Allow anonymous access to OpenERP.
+==================================
+ """,
+ 'author': 'OpenERP SA',
+ 'version': '1.0',
+ 'category': 'Authentication',
+ 'website': 'http://www.openerp.com',
+ 'installable': True,
+ 'depends': ['web'],
+ 'data': ['auth_anonymous.xml'],
+ 'js': ['static/src/js/auth_anonymous.js'],
+ 'qweb': ['static/src/xml/auth_anonymous.xml'],
}
diff --git a/addons/auth_ldap/__openerp__.py b/addons/auth_ldap/__openerp__.py
index 4e5bc811dca..aa7c93e4146 100644
--- a/addons/auth_ldap/__openerp__.py
+++ b/addons/auth_ldap/__openerp__.py
@@ -19,18 +19,18 @@
##############################################################################
{
- "name" : "Authentication via LDAP",
- "version" : "1.0",
- "depends" : ["base"],
- "images" : ["images/ldap_configuration.jpeg"],
- "author" : "OpenERP SA",
- "description": """
+ 'name' : 'Authentication via LDAP',
+ 'version' : '1.0',
+ 'depends' : ['base'],
+ 'images' : ['images/ldap_configuration.jpeg'],
+ 'author' : 'OpenERP SA',
+ 'description': """
Adds support for authentication by LDAP server.
===============================================
This module allows users to login with their LDAP username and password, and
will automatically create OpenERP users for them on the fly.
-Note: This module only work on servers who have Python's ``ldap`` module installed.
+**Note:** This module only work on servers who have Python's ``ldap`` module installed.
Configuration:
--------------
@@ -64,7 +64,7 @@ It is also possible to have local OpenERP users in the database along with
LDAP-authenticated users (the Administrator account is one obvious example).
Here is how it works:
-
+---------------------
* The system first attempts to authenticate users against the local OpenERP
database;
* if this authentication fails (for example because the user has no local
@@ -84,7 +84,7 @@ Template*. If set, this user will be used as template to create the local users
whenever someone authenticates for the first time via LDAP authentication. This
allows pre-setting the default groups and menus of the first-time users.
-Warning: if you set a password for the user template, this password will be
+**Warning:** if you set a password for the user template, this password will be
assigned as local password for each new LDAP user, effectively setting
a *master password* for these users (until manually changed). You
usually do not want this. One easy way to setup a template user is to
@@ -98,19 +98,17 @@ Interaction with base_crypt:
The base_crypt module is not compatible with this module, and will disable LDAP
authentication if installed at the same time.
""",
-
-
- "website" : "http://www.openerp.com",
- "category" : "Authentication",
- "data" : [
- "users_ldap_view.xml",
- "user_ldap_installer.xml",
- "security/ir.model.access.csv",
+ 'website' : 'http://www.openerp.com',
+ 'category' : 'Authentication',
+ 'data' : [
+ 'users_ldap_view.xml',
+ 'user_ldap_installer.xml',
+ 'security/ir.model.access.csv',
],
- "auto_install": False,
- "installable": True,
- "certificate" : "001141446349334700221",
- "external_dependencies" : {
+ 'auto_install': False,
+ 'installable': True,
+ 'certificate' : '001141446349334700221',
+ 'external_dependencies' : {
'python' : ['ldap'],
}
}
diff --git a/addons/auth_oauth/__openerp__.py b/addons/auth_oauth/__openerp__.py
index 585dc48e11e..f0be1234721 100644
--- a/addons/auth_oauth/__openerp__.py
+++ b/addons/auth_oauth/__openerp__.py
@@ -24,27 +24,21 @@
'name': 'OAuth2 Authentication',
'version': '1.0',
'category': 'Tools',
- 'description': """Allow users to login through Google OAuth2.""",
+ 'description': """
+Allow users to login through Google OAuth2.
+===========================================
+""",
'author': 'Victor Tabuenca',
'maintainer': 'OpenERP s.a.',
'website': 'http://www.openerp.com',
'depends': ['base', 'web', 'base_setup'],
'data': [
'auth_oauth_data.xml',
+ 'auth_oauth_view.xml'
],
- 'update_xml': [
- 'auth_oauth_view.xml',
- 'res_config.xml',
- ],
- 'js': [
- 'static/src/js/auth_oauth.js',
- ],
- 'css': [
- 'static/lib/zocial/css/zocial.css',
- ],
- 'qweb': [
- 'static/src/xml/auth_oauth.xml',
- ],
+ 'js': ['static/src/js/auth_oauth.js'],
+ 'css': ['static/lib/zocial/css/zocial.css'],
+ 'qweb': ['static/src/xml/auth_oauth.xml'],
'installable': True,
'auto_install': False,
}
diff --git a/addons/auth_openid/__openerp__.py b/addons/auth_openid/__openerp__.py
index cd1a12fce7f..10c77f29965 100644
--- a/addons/auth_openid/__openerp__.py
+++ b/addons/auth_openid/__openerp__.py
@@ -24,23 +24,18 @@
'name': 'OpenID Authentification',
'version': '2.0',
'category': 'Tools',
- 'description': """Allow users to login through OpenID.""",
+ 'description': """
+Allow users to login through OpenID.
+====================================
+""",
'author': 'OpenERP s.a.',
'maintainer': 'OpenERP s.a.',
'website': 'http://www.openerp.com',
'depends': ['base', 'web'],
- 'data': [
- 'res_users.xml',
- ],
- 'js': [
- 'static/src/js/auth_openid.js',
- ],
- 'css': [
- 'static/src/css/openid.css',
- ],
- 'qweb': [
- 'static/src/xml/auth_openid.xml',
- ],
+ 'data': ['res_users.xml'],
+ 'js': ['static/src/js/auth_openid.js'],
+ 'css': ['static/src/css/openid.css'],
+ 'qweb': ['static/src/xml/auth_openid.xml'],
'external_dependencies': {
'python' : ['openid'],
},
diff --git a/addons/auth_reset_password/__openerp__.py b/addons/auth_reset_password/__openerp__.py
index 76528bbfeb8..ccf5ca53a21 100644
--- a/addons/auth_reset_password/__openerp__.py
+++ b/addons/auth_reset_password/__openerp__.py
@@ -1,22 +1,17 @@
{
- 'name': 'Reset Password',
- 'description': 'Allow users to reset their password from the login page',
- 'author': 'OpenERP SA',
- 'version': '1.0',
- 'category': 'Authentication',
- 'website': 'http://www.openerp.com',
- 'installable': True,
- 'depends': ['auth_anonymous', 'email_template'],
- 'data': [
- 'auth_reset_password.xml',
- ],
- 'js': [
- 'static/src/js/reset_password.js',
- ],
- 'css': [
- 'static/src/css/reset_password.css',
- ],
- 'qweb': [
- 'static/src/xml/reset_password.xml',
- ],
+ 'name': 'Reset Password',
+ 'description': """
+Allow users to reset their password from the login page.
+========================================================
+""",
+ 'author': 'OpenERP SA',
+ 'version': '1.0',
+ 'category': 'Authentication',
+ 'website': 'http://www.openerp.com',
+ 'installable': True,
+ 'depends': ['auth_anonymous', 'email_template'],
+ 'data': ['auth_reset_password.xml'],
+ 'js': ['static/src/js/reset_password.js'],
+ 'css': ['static/src/css/reset_password.css'],
+ 'qweb': ['static/src/xml/reset_password.xml'],
}
diff --git a/addons/auth_signup/__openerp__.py b/addons/auth_signup/__openerp__.py
index 765a36ef252..5f889bfb119 100644
--- a/addons/auth_signup/__openerp__.py
+++ b/addons/auth_signup/__openerp__.py
@@ -21,20 +21,17 @@
{
'name': 'Signup',
- 'description': 'Allow users to sign up',
+ 'description': """
+Allow users to sign up.
+=======================
+ """,
'author': 'OpenERP SA',
'version': '1.0',
'category': 'Authentication',
'website': 'http://www.openerp.com',
'installable': True,
'depends': ['base_setup'],
- 'data': [
- 'res_config.xml',
- ],
- 'js': [
- 'static/src/js/auth_signup.js',
- ],
- 'qweb': [
- 'static/src/xml/auth_signup.xml',
- ],
+ 'data': ['res_config.xml'],
+ 'js': ['static/src/js/auth_signup.js'],
+ 'qweb': ['static/src/xml/auth_signup.xml'],
}
diff --git a/addons/base_action_rule/__openerp__.py b/addons/base_action_rule/__openerp__.py
index eff92685313..c9483f00360 100644
--- a/addons/base_action_rule/__openerp__.py
+++ b/addons/base_action_rule/__openerp__.py
@@ -29,21 +29,19 @@ This module allows to implement action rules for any object.
Use automated actions to automatically trigger actions for various screens.
-Example: A lead created by a specific user may be automatically set to a specific
+**Example:** A lead created by a specific user may be automatically set to a specific
sales team, or an opportunity which still has status pending after 14 days might
trigger an automatic reminder email.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['base', 'mail'],
- 'init_xml': [
- 'base_action_rule_data.xml'
- ],
- 'update_xml': [
+ 'data': [
'base_action_rule_view.xml',
'security/ir.model.access.csv',
+ 'base_action_rule_data.xml'
],
- 'demo_xml': [],
+ 'demo': [],
'installable': True,
'auto_install': False,
'certificate' : '001017908446466333429',
diff --git a/addons/base_calendar/__openerp__.py b/addons/base_calendar/__openerp__.py
index 58f666baa0d..48ec05e2e92 100644
--- a/addons/base_calendar/__openerp__.py
+++ b/addons/base_calendar/__openerp__.py
@@ -20,14 +20,15 @@
##############################################################################
{
- "name": "Calendar Layer",
- "version": "1.0",
- "depends": ["base", "base_status", "mail", "base_action_rule"],
+ 'name': 'Calendar Layer',
+ 'version': '1.0',
+ 'depends': ['base', 'base_status', 'mail', 'base_action_rule'],
'description': """
This is a full-featured calendar system.
========================================
It supports:
+------------
- Calendar of events
- Alerts (create requests)
- Recurring events
@@ -35,25 +36,23 @@ It supports:
If you need to manage your meetings, you should install the CRM module.
""",
- "author": "OpenERP SA",
+ 'author': 'OpenERP SA',
'category': 'Hidden/Dependency',
'website': 'http://www.openerp.com',
- "init_xml": [
- 'base_calendar_data.xml',
- 'crm_meeting_data.xml',
- ],
- "demo_xml": ['crm_meeting_demo.xml'],
- "update_xml": [
+ 'demo': ['crm_meeting_demo.xml'],
+ 'data': [
'security/calendar_security.xml',
'security/ir.model.access.csv',
'wizard/base_calendar_invite_attendee_view.xml',
'base_calendar_view.xml',
'crm_meeting_view.xml',
+ 'base_calendar_data.xml',
+ 'crm_meeting_data.xml',
],
- "test" : ['test/base_calendar_test.yml'],
- "installable": True,
- "auto_install": False,
- "certificate": "00694071962960352821",
+ 'test' : ['test/base_calendar_test.yml'],
+ 'installable': True,
+ 'auto_install': False,
+ 'certificate': '00694071962960352821',
'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg',],
}
diff --git a/addons/base_calendar/crm_meeting_data.xml b/addons/base_calendar/crm_meeting_data.xml
index 44838514935..d1cffddc403 100644
--- a/addons/base_calendar/crm_meeting_data.xml
+++ b/addons/base_calendar/crm_meeting_data.xml
@@ -16,6 +16,14 @@
Off-site Meeting
+
+ Open Discussion
+
+
+
+ Feedback Meeting
+
+
Meetingcrm.meeting
diff --git a/addons/base_calendar/crm_meeting_demo.xml b/addons/base_calendar/crm_meeting_demo.xml
index 221653e5fc3..6c098e2f949 100644
--- a/addons/base_calendar/crm_meeting_demo.xml
+++ b/addons/base_calendar/crm_meeting_demo.xml
@@ -27,7 +27,7 @@
- draft
+ open
@@ -39,7 +39,7 @@
- done
+ open
@@ -61,7 +61,7 @@
- draft
+ open
@@ -72,7 +72,7 @@
- done
+ open
diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml
index 51d17ed43f8..6009251a460 100644
--- a/addons/base_calendar/crm_meeting_view.xml
+++ b/addons/base_calendar/crm_meeting_view.xml
@@ -70,7 +70,7 @@
-
+
-
-
+
+
@@ -630,6 +634,9 @@
+
+
+
@@ -773,10 +780,10 @@
-
+
-
-
+
+
@@ -791,14 +798,11 @@
-
-
-
@@ -877,7 +881,7 @@
class="oe_highlight"/>
-
+
+
+
+
+
diff --git a/addons/point_of_sale/report/pos_sales_user.py b/addons/point_of_sale/report/pos_sales_user.py
index e49528441b9..6ac2e1892c1 100644
--- a/addons/point_of_sale/report/pos_sales_user.py
+++ b/addons/point_of_sale/report/pos_sales_user.py
@@ -37,14 +37,14 @@ class pos_sales_user(report_sxw.rml_parse):
dt1 = form['date_start'] + ' 00:00:00'
dt2 = form['date_end'] + ' 23:59:59'
data={}
- self.cr.execute("select po.name as pos,po.date_order,ru.name as user,po.state,rc.name " \
- "from pos_order as po,res_users as ru,res_company as rc " \
+ self.cr.execute("select po.name as pos,po.date_order,rp.name as user,po.state,rc.name " \
+ "from pos_order as po,res_users as ru,res_company as rc, res_partner as rp " \
"where po.date_order >= %s and po.date_order <= %s " \
- "and po.company_id=rc.id and po.user_id=ru.id and po.user_id IN %s " \
+ "and po.company_id=rc.id and po.user_id=ru.id and po.user_id IN %s and ru.partner_id = rp.id" \
,(dt1,dt2,tuple(form['user_id'])))
return self.cr.dictfetchall()
report_sxw.report_sxw('report.pos.sales.user', 'pos.order', 'addons/point_of_sale/report/pos_sales_user.rml', parser=pos_sales_user,header='internal')
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/point_of_sale/res_users.py b/addons/point_of_sale/res_users.py
index d64f05d1391..f1c613d5f06 100644
--- a/addons/point_of_sale/res_users.py
+++ b/addons/point_of_sale/res_users.py
@@ -1,38 +1,8 @@
#!/usr/bin/env python
from osv import osv, fields
import math
+import openerp.addons.product.product
-def is_pair(x):
- return not x%2
-# This code is a duplicate of product#check_ean function
-def check_ean(eancode):
- if not eancode:
- return True
- if len(eancode) <> 13:
- return False
- try:
- int(eancode)
- except:
- return False
- oddsum=0
- evensum=0
- total=0
- eanvalue=eancode
- reversevalue = eanvalue[::-1]
- finalean=reversevalue[1:]
-
- for i in range(len(finalean)):
- if is_pair(i):
- oddsum += int(finalean[i])
- else:
- evensum += int(finalean[i])
- total=(oddsum * 3) + evensum
-
- check = int(10 - math.ceil(total % 10.0)) %10
-
- if check != int(eancode[-1]):
- return False
- return True
class res_users(osv.osv):
_inherit = 'res.users'
@@ -43,10 +13,22 @@ class res_users(osv.osv):
def _check_ean(self, cr, uid, ids, context=None):
return all(
- check_ean(user.ean13) == True
+ openerp.addons.product.product.check_ean(user.ean13) == True
for user in self.browse(cr, uid, ids, context=context)
)
+ def edit_ean(self, cr, uid, ids, context):
+ return {
+ 'name': "Edit Ean",
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'pos.ean_wizard',
+ 'target' : 'new',
+ 'view_id': False,
+ 'context':context,
+ }
+
_constraints = [
(_check_ean, "Error: Invalid ean code", ['ean13'],),
]
diff --git a/addons/point_of_sale/res_users_view.xml b/addons/point_of_sale/res_users_view.xml
index f561ccf9b80..2a510b1d575 100644
--- a/addons/point_of_sale/res_users_view.xml
+++ b/addons/point_of_sale/res_users_view.xml
@@ -20,8 +20,11 @@
-
-
+
+
+
+
+
diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv
index 06673fc3da8..74b4bbf5753 100644
--- a/addons/point_of_sale/security/ir.model.access.csv
+++ b/addons/point_of_sale/security/ir.model.access.csv
@@ -60,7 +60,8 @@ access_product_uom_manager,product.uom manager,product.model_product_uom,group_p
access_res_partner_manager,res.partner manager,base.model_res_partner,group_pos_manager,1,0,0,0
access_product_category_manager,product.category manager,product.model_product_category,group_pos_manager,1,1,1,1
access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,group_pos_manager,1,0,0,0
-access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,"1"""
-access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,"0"""
+access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,1
+access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,0
access_pos_session_user,pos.session user,model_pos_session,group_pos_user,1,1,1,0
access_pos_config_user,pos.config user,model_pos_config,group_pos_user,1,1,1,0
+access_ir_sequence_manager,ir.sequence manager,base.model_ir_sequence,group_pos_manager,1,1,1,1
diff --git a/addons/point_of_sale/static/lib/backbone/backbone-0.5.3.js b/addons/point_of_sale/static/lib/backbone/backbone-0.5.3.js
deleted file mode 100644
index 8958c798466..00000000000
--- a/addons/point_of_sale/static/lib/backbone/backbone-0.5.3.js
+++ /dev/null
@@ -1,1158 +0,0 @@
-// Backbone.js 0.5.3
-// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://documentcloud.github.com/backbone
-
-(function(){
-
- // Initial Setup
- // -------------
-
- // Save a reference to the global object.
- var root = this;
-
- // Save the previous value of the `Backbone` variable.
- var previousBackbone = root.Backbone;
-
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both CommonJS and the browser.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
- // Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '0.5.3';
-
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
-
- // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
- var $ = root.jQuery || root.Zepto;
-
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
- // to its previous owner. Returns a reference to this Backbone object.
- Backbone.noConflict = function() {
- root.Backbone = previousBackbone;
- return this;
- };
-
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
- // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
- // `X-Http-Method-Override` header.
- Backbone.emulateHTTP = false;
-
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
- // `application/json` requests ... will encode the body as
- // `application/x-www-form-urlencoded` instead and will send the model in a
- // form param named `model`.
- Backbone.emulateJSON = false;
-
- // Backbone.Events
- // -----------------
-
- // A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may `bind` or `unbind` a callback function to an event;
- // `trigger`-ing an event fires all callbacks in succession.
- //
- // var object = {};
- // _.extend(object, Backbone.Events);
- // object.bind('expand', function(){ alert('expanded'); });
- // object.trigger('expand');
- //
- Backbone.Events = {
-
- // Bind an event, specified by a string name, `ev`, to a `callback` function.
- // Passing `"all"` will bind the callback to all events fired.
- bind : function(ev, callback, context) {
- var calls = this._callbacks || (this._callbacks = {});
- var list = calls[ev] || (calls[ev] = []);
- list.push([callback, context]);
- return this;
- },
-
- // Remove one or many callbacks. If `callback` is null, removes all
- // callbacks for the event. If `ev` is null, removes all bound callbacks
- // for all events.
- unbind : function(ev, callback) {
- var calls;
- if (!ev) {
- this._callbacks = {};
- } else if (calls = this._callbacks) {
- if (!callback) {
- calls[ev] = [];
- } else {
- var list = calls[ev];
- if (!list) return this;
- for (var i = 0, l = list.length; i < l; i++) {
- if (list[i] && callback === list[i][0]) {
- list[i] = null;
- break;
- }
- }
- }
- }
- return this;
- },
-
- // Trigger an event, firing all bound callbacks. Callbacks are passed the
- // same arguments as `trigger` is, apart from the event name.
- // Listening for `"all"` passes the true event name as the first argument.
- trigger : function(eventName) {
- var list, calls, ev, callback, args;
- var both = 2;
- if (!(calls = this._callbacks)) return this;
- while (both--) {
- ev = both ? eventName : 'all';
- if (list = calls[ev]) {
- for (var i = 0, l = list.length; i < l; i++) {
- if (!(callback = list[i])) {
- list.splice(i, 1); i--; l--;
- } else {
- args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
- callback[0].apply(callback[1] || this, args);
- }
- }
- }
- }
- return this;
- }
-
- };
-
- // Backbone.Model
- // --------------
-
- // Create a new model, with defined attributes. A client id (`cid`)
- // is automatically generated and assigned for you.
- Backbone.Model = function(attributes, options) {
- var defaults;
- attributes || (attributes = {});
- if (defaults = this.defaults) {
- if (_.isFunction(defaults)) defaults = defaults.call(this);
- attributes = _.extend({}, defaults, attributes);
- }
- this.attributes = {};
- this._escapedAttributes = {};
- this.cid = _.uniqueId('c');
- this.set(attributes, {silent : true});
- this._changed = false;
- this._previousAttributes = _.clone(this.attributes);
- if (options && options.collection) this.collection = options.collection;
- this.initialize(attributes, options);
- };
-
- // Attach all inheritable methods to the Model prototype.
- _.extend(Backbone.Model.prototype, Backbone.Events, {
-
- // A snapshot of the model's previous attributes, taken immediately
- // after the last `"change"` event was fired.
- _previousAttributes : null,
-
- // Has the item been changed since the last `"change"` event?
- _changed : false,
-
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
- // CouchDB users may want to set this to `"_id"`.
- idAttribute : 'id',
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // Return a copy of the model's `attributes` object.
- toJSON : function() {
- return _.clone(this.attributes);
- },
-
- // Get the value of an attribute.
- get : function(attr) {
- return this.attributes[attr];
- },
-
- // Get the HTML-escaped value of an attribute.
- escape : function(attr) {
- var html;
- if (html = this._escapedAttributes[attr]) return html;
- var val = this.attributes[attr];
- return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
- },
-
- // Returns `true` if the attribute contains a value that is not null
- // or undefined.
- has : function(attr) {
- return this.attributes[attr] != null;
- },
-
- // Set a hash of model attributes on the object, firing `"change"` unless you
- // choose to silence it.
- set : function(attrs, options) {
-
- // Extract attributes and options.
- options || (options = {});
- if (!attrs) return this;
- if (attrs.attributes) attrs = attrs.attributes;
- var now = this.attributes, escaped = this._escapedAttributes;
-
- // Run validation.
- if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
- // We're about to start triggering change events.
- var alreadyChanging = this._changing;
- this._changing = true;
-
- // Update attributes.
- for (var attr in attrs) {
- var val = attrs[attr];
- if (!_.isEqual(now[attr], val)) {
- now[attr] = val;
- delete escaped[attr];
- this._changed = true;
- if (!options.silent) this.trigger('change:' + attr, this, val, options);
- }
- }
-
- // Fire the `"change"` event, if the model has been changed.
- if (!alreadyChanging && !options.silent && this._changed) this.change(options);
- this._changing = false;
- return this;
- },
-
- // Remove an attribute from the model, firing `"change"` unless you choose
- // to silence it. `unset` is a noop if the attribute doesn't exist.
- unset : function(attr, options) {
- if (!(attr in this.attributes)) return this;
- options || (options = {});
- var value = this.attributes[attr];
-
- // Run validation.
- var validObj = {};
- validObj[attr] = void 0;
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
-
- // Remove the attribute.
- delete this.attributes[attr];
- delete this._escapedAttributes[attr];
- if (attr == this.idAttribute) delete this.id;
- this._changed = true;
- if (!options.silent) {
- this.trigger('change:' + attr, this, void 0, options);
- this.change(options);
- }
- return this;
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear : function(options) {
- options || (options = {});
- var attr;
- var old = this.attributes;
-
- // Run validation.
- var validObj = {};
- for (attr in old) validObj[attr] = void 0;
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
-
- this.attributes = {};
- this._escapedAttributes = {};
- this._changed = true;
- if (!options.silent) {
- for (attr in old) {
- this.trigger('change:' + attr, this, void 0, options);
- }
- this.change(options);
- }
- return this;
- },
-
- // Fetch the model from the server. If the server's representation of the
- // model differs from its current attributes, they will be overriden,
- // triggering a `"change"` event.
- fetch : function(options) {
- options || (options = {});
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp);
- };
- options.error = wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Set a hash of model attributes, and sync the model to the server.
- // If the server returns an attributes hash that differs, the model's
- // state will be `set` again.
- save : function(attrs, options) {
- options || (options = {});
- if (attrs && !this.set(attrs, options)) return false;
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp, xhr);
- };
- options.error = wrapError(options.error, model, options);
- var method = this.isNew() ? 'create' : 'update';
- return (this.sync || Backbone.sync).call(this, method, this, options);
- },
-
- // Destroy this model on the server if it was already persisted. Upon success, the model is removed
- // from its collection, if it has one.
- destroy : function(options) {
- options || (options = {});
- if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
- var model = this;
- var success = options.success;
- options.success = function(resp) {
- model.trigger('destroy', model, model.collection, options);
- if (success) success(model, resp);
- };
- options.error = wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'delete', this, options);
- },
-
- // Default URL for the model's representation on the server -- if you're
- // using Backbone's restful methods, override this to change the endpoint
- // that will be called.
- url : function() {
- var base = getUrl(this.collection) || this.urlRoot || urlError();
- if (this.isNew()) return base;
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
- },
-
- // **parse** converts a response into the hash of attributes to be `set` on
- // the model. The default implementation is just to pass the response along.
- parse : function(resp, xhr) {
- return resp;
- },
-
- // Create a new model with identical attributes to this one.
- clone : function() {
- return new this.constructor(this);
- },
-
- // A model is new if it has never been saved to the server, and lacks an id.
- isNew : function() {
- return this.id == null;
- },
-
- // Call this method to manually fire a `change` event for this model.
- // Calling this will cause all objects observing the model to update.
- change : function(options) {
- this.trigger('change', this, options);
- this._previousAttributes = _.clone(this.attributes);
- this._changed = false;
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged : function(attr) {
- if (attr) return this._previousAttributes[attr] != this.attributes[attr];
- return this._changed;
- },
-
- // Return an object containing all the attributes that have changed, or false
- // if there are no changed attributes. Useful for determining what parts of a
- // view need to be updated and/or what attributes need to be persisted to
- // the server.
- changedAttributes : function(now) {
- now || (now = this.attributes);
- var old = this._previousAttributes;
- var changed = false;
- for (var attr in now) {
- if (!_.isEqual(old[attr], now[attr])) {
- changed = changed || {};
- changed[attr] = now[attr];
- }
- }
- return changed;
- },
-
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous : function(attr) {
- if (!attr || !this._previousAttributes) return null;
- return this._previousAttributes[attr];
- },
-
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes : function() {
- return _.clone(this._previousAttributes);
- },
-
- // Run validation against a set of incoming attributes, returning `true`
- // if all is well. If a specific `error` callback has been passed,
- // call that instead of firing the general `"error"` event.
- _performValidation : function(attrs, options) {
- var error = this.validate(attrs);
- if (error) {
- if (options.error) {
- options.error(this, error, options);
- } else {
- this.trigger('error', this, error, options);
- }
- return false;
- }
- return true;
- }
-
- });
-
- // Backbone.Collection
- // -------------------
-
- // Provides a standard collection class for our sets of models, ordered
- // or unordered. If a `comparator` is specified, the Collection will maintain
- // its models in sort order, as they're added and removed.
- Backbone.Collection = function(models, options) {
- options || (options = {});
- if (options.comparator) this.comparator = options.comparator;
- _.bindAll(this, '_onModelEvent', '_removeReference');
- this._reset();
- if (models) this.reset(models, {silent: true});
- this.initialize.apply(this, arguments);
- };
-
- // Define the Collection's inheritable methods.
- _.extend(Backbone.Collection.prototype, Backbone.Events, {
-
- // The default model for a collection is just a **Backbone.Model**.
- // This should be overridden in most cases.
- model : Backbone.Model,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // The JSON representation of a Collection is an array of the
- // models' attributes.
- toJSON : function() {
- return this.map(function(model){ return model.toJSON(); });
- },
-
- // Add a model, or list of models to the set. Pass **silent** to avoid
- // firing the `added` event for every new model.
- add : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._add(models[i], options);
- }
- } else {
- this._add(models, options);
- }
- return this;
- },
-
- // Remove a model, or a list of models from the set. Pass silent to avoid
- // firing the `removed` event for every model removed.
- remove : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._remove(models[i], options);
- }
- } else {
- this._remove(models, options);
- }
- return this;
- },
-
- // Get a model from the set by id.
- get : function(id) {
- if (id == null) return null;
- return this._byId[id.id != null ? id.id : id];
- },
-
- // Get a model from the set by client id.
- getByCid : function(cid) {
- return cid && this._byCid[cid.cid || cid];
- },
-
- // Get the model at the given index.
- at: function(index) {
- return this.models[index];
- },
-
- // Force the collection to re-sort itself. You don't need to call this under normal
- // circumstances, as the set will maintain sort order as each item is added.
- sort : function(options) {
- options || (options = {});
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- this.models = this.sortBy(this.comparator);
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Pluck an attribute from each model in the collection.
- pluck : function(attr) {
- return _.map(this.models, function(model){ return model.get(attr); });
- },
-
- // When you have more items than you want to add or remove individually,
- // you can reset the entire set with a new list of models, without firing
- // any `added` or `removed` events. Fires `reset` when finished.
- reset : function(models, options) {
- models || (models = []);
- options || (options = {});
- this.each(this._removeReference);
- this._reset();
- this.add(models, {silent: true});
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Fetch the default set of models for this collection, resetting the
- // collection when they arrive. If `add: true` is passed, appends the
- // models to the collection instead of resetting.
- fetch : function(options) {
- options || (options = {});
- var collection = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
- if (success) success(collection, resp);
- };
- options.error = wrapError(options.error, collection, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Create a new instance of a model in this collection. After the model
- // has been created on the server, it will be added to the collection.
- // Returns the model, or 'false' if validation on a new model fails.
- create : function(model, options) {
- var coll = this;
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var success = options.success;
- options.success = function(nextModel, resp, xhr) {
- coll.add(nextModel, options);
- if (success) success(nextModel, resp, xhr);
- };
- model.save(null, options);
- return model;
- },
-
- // **parse** converts a response into a list of models to be added to the
- // collection. The default implementation is just to pass it through.
- parse : function(resp, xhr) {
- return resp;
- },
-
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
- // underscore methods are proxied because it relies on the underscore
- // constructor.
- chain: function () {
- return _(this.models).chain();
- },
-
- // Reset all internal state. Called when the collection is reset.
- _reset : function(options) {
- this.length = 0;
- this.models = [];
- this._byId = {};
- this._byCid = {};
- },
-
- // Prepare a model to be added to this collection
- _prepareModel: function(model, options) {
- if (!(model instanceof Backbone.Model)) {
- var attrs = model;
- model = new this.model(attrs, {collection: this});
- if (model.validate && !model._performValidation(attrs, options)) model = false;
- } else if (!model.collection) {
- model.collection = this;
- }
- return model;
- },
-
- // Internal implementation of adding a single model to the set, updating
- // hash indexes for `id` and `cid` lookups.
- // Returns the model, or 'false' if validation on a new model fails.
- _add : function(model, options) {
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var already = this.getByCid(model);
- if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
- this._byId[model.id] = model;
- this._byCid[model.cid] = model;
- var index = options.at != null ? options.at :
- this.comparator ? this.sortedIndex(model, this.comparator) :
- this.length;
- this.models.splice(index, 0, model);
- model.bind('all', this._onModelEvent);
- this.length++;
- if (!options.silent) model.trigger('add', model, this, options);
- return model;
- },
-
- // Internal implementation of removing a single model from the set, updating
- // hash indexes for `id` and `cid` lookups.
- _remove : function(model, options) {
- options || (options = {});
- model = this.getByCid(model) || this.get(model);
- if (!model) return null;
- delete this._byId[model.id];
- delete this._byCid[model.cid];
- this.models.splice(this.indexOf(model), 1);
- this.length--;
- if (!options.silent) model.trigger('remove', model, this, options);
- this._removeReference(model);
- return model;
- },
-
- // Internal method to remove a model's ties to a collection.
- _removeReference : function(model) {
- if (this == model.collection) {
- delete model.collection;
- }
- model.unbind('all', this._onModelEvent);
- },
-
- // Internal method called every time a model in the set fires an event.
- // Sets need to update their indexes when models change ids. All other
- // events simply proxy through. "add" and "remove" events that originate
- // in other collections are ignored.
- _onModelEvent : function(ev, model, collection, options) {
- if ((ev == 'add' || ev == 'remove') && collection != this) return;
- if (ev == 'destroy') {
- this._remove(model, options);
- }
- if (model && ev === 'change:' + model.idAttribute) {
- delete this._byId[model.previous(model.idAttribute)];
- this._byId[model.id] = model;
- }
- this.trigger.apply(this, arguments);
- }
-
- });
-
- // Underscore methods that we want to implement on the Collection.
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
- 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
- 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
- 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
-
- // Mix in each Underscore method as a proxy to `Collection#models`.
- _.each(methods, function(method) {
- Backbone.Collection.prototype[method] = function() {
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
- };
- });
-
- // Backbone.Router
- // -------------------
-
- // Routers map faux-URLs to actions, and fire events when routes are
- // matched. Creating a new one sets its `routes` hash, if not set statically.
- Backbone.Router = function(options) {
- options || (options = {});
- if (options.routes) this.routes = options.routes;
- this._bindRoutes();
- this.initialize.apply(this, arguments);
- };
-
- // Cached regular expressions for matching named param parts and splatted
- // parts of route strings.
- var namedParam = /:([\w\d]+)/g;
- var splatParam = /\*([\w\d]+)/g;
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
-
- // Set up all inheritable **Backbone.Router** properties and methods.
- _.extend(Backbone.Router.prototype, Backbone.Events, {
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // Manually bind a single named route to a callback. For example:
- //
- // this.route('search/:query/p:num', 'search', function(query, num) {
- // ...
- // });
- //
- route : function(route, name, callback) {
- Backbone.history || (Backbone.history = new Backbone.History);
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
- Backbone.history.route(route, _.bind(function(fragment) {
- var args = this._extractParameters(route, fragment);
- callback.apply(this, args);
- this.trigger.apply(this, ['route:' + name].concat(args));
- }, this));
- },
-
- // Simple proxy to `Backbone.history` to save a fragment into the history.
- navigate : function(fragment, triggerRoute) {
- Backbone.history.navigate(fragment, triggerRoute);
- },
-
- // Bind all defined routes to `Backbone.history`. We have to reverse the
- // order of the routes here to support behavior where the most general
- // routes can be defined at the bottom of the route map.
- _bindRoutes : function() {
- if (!this.routes) return;
- var routes = [];
- for (var route in this.routes) {
- routes.unshift([route, this.routes[route]]);
- }
- for (var i = 0, l = routes.length; i < l; i++) {
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
- }
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp : function(route) {
- route = route.replace(escapeRegExp, "\\$&")
- .replace(namedParam, "([^\/]*)")
- .replace(splatParam, "(.*?)");
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted parameters.
- _extractParameters : function(route, fragment) {
- return route.exec(fragment).slice(1);
- }
-
- });
-
- // Backbone.History
- // ----------------
-
- // Handles cross-browser history management, based on URL fragments. If the
- // browser does not support `onhashchange`, falls back to polling.
- Backbone.History = function() {
- this.handlers = [];
- _.bindAll(this, 'checkUrl');
- };
-
- // Cached regex for cleaning hashes.
- var hashStrip = /^#*/;
-
- // Cached regex for detecting MSIE.
- var isExplorer = /msie [\w.]+/;
-
- // Has the history handling already been started?
- var historyStarted = false;
-
- // Set up all inheritable **Backbone.History** properties and methods.
- _.extend(Backbone.History.prototype, {
-
- // The default interval to poll for hash changes, if necessary, is
- // twenty times a second.
- interval: 50,
-
- // Get the cross-browser normalized URL fragment, either from the URL,
- // the hash, or the override.
- getFragment : function(fragment, forcePushState) {
- if (fragment == null) {
- if (this._hasPushState || forcePushState) {
- fragment = window.location.pathname;
- var search = window.location.search;
- if (search) fragment += search;
- if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
- } else {
- fragment = window.location.hash;
- }
- }
- return decodeURIComponent(fragment.replace(hashStrip, ''));
- },
-
- // Start the hash change handling, returning `true` if the current URL matches
- // an existing route, and `false` otherwise.
- start : function(options) {
-
- // Figure out the initial configuration. Do we need an iframe?
- // Is pushState desired ... is it available?
- if (historyStarted) throw new Error("Backbone.history has already been started");
- this.options = _.extend({}, {root: '/'}, this.options, options);
- this._wantsPushState = !!this.options.pushState;
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
- var fragment = this.getFragment();
- var docMode = document.documentMode;
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
- if (oldIE) {
- this.iframe = $('').hide().appendTo('body')[0].contentWindow;
- this.navigate(fragment);
- }
-
- // Depending on whether we're using pushState or hashes, and whether
- // 'onhashchange' is supported, determine how we check the URL state.
- if (this._hasPushState) {
- $(window).bind('popstate', this.checkUrl);
- } else if ('onhashchange' in window && !oldIE) {
- $(window).bind('hashchange', this.checkUrl);
- } else {
- setInterval(this.checkUrl, this.interval);
- }
-
- // Determine if we need to change the base url, for a pushState link
- // opened by a non-pushState browser.
- this.fragment = fragment;
- historyStarted = true;
- var loc = window.location;
- var atRoot = loc.pathname == this.options.root;
- if (this._wantsPushState && !this._hasPushState && !atRoot) {
- this.fragment = this.getFragment(null, true);
- window.location.replace(this.options.root + '#' + this.fragment);
- // Return immediately as browser will do redirect to new url
- return true;
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
- this.fragment = loc.hash.replace(hashStrip, '');
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
- }
-
- if (!this.options.silent) {
- return this.loadUrl();
- }
- },
-
- // Add a route to be tested when the fragment changes. Routes added later may
- // override previous routes.
- route : function(route, callback) {
- this.handlers.unshift({route : route, callback : callback});
- },
-
- // Checks the current URL to see if it has changed, and if it has,
- // calls `loadUrl`, normalizing across the hidden iframe.
- checkUrl : function(e) {
- var current = this.getFragment();
- if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
- if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
- if (this.iframe) this.navigate(current);
- this.loadUrl() || this.loadUrl(window.location.hash);
- },
-
- // Attempt to load the current URL fragment. If a route succeeds with a
- // match, returns `true`. If no defined routes matches the fragment,
- // returns `false`.
- loadUrl : function(fragmentOverride) {
- var fragment = this.fragment = this.getFragment(fragmentOverride);
- var matched = _.any(this.handlers, function(handler) {
- if (handler.route.test(fragment)) {
- handler.callback(fragment);
- return true;
- }
- });
- return matched;
- },
-
- // Save a fragment into the hash history. You are responsible for properly
- // URL-encoding the fragment in advance. This does not trigger
- // a `hashchange` event.
- navigate : function(fragment, triggerRoute) {
- var frag = (fragment || '').replace(hashStrip, '');
- if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
- if (this._hasPushState) {
- var loc = window.location;
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
- this.fragment = frag;
- window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
- } else {
- window.location.hash = this.fragment = frag;
- if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
- this.iframe.document.open().close();
- this.iframe.location.hash = frag;
- }
- }
- if (triggerRoute) this.loadUrl(fragment);
- }
-
- });
-
- // Backbone.View
- // -------------
-
- // Creating a Backbone.View creates its initial element outside of the DOM,
- // if an existing element is not provided...
- Backbone.View = function(options) {
- this.cid = _.uniqueId('view');
- this._configure(options || {});
- this._ensureElement();
- this.delegateEvents();
- this.initialize.apply(this, arguments);
- };
-
- // Element lookup, scoped to DOM elements within the current view.
- // This should be prefered to global lookups, if you're dealing with
- // a specific view.
- var selectorDelegate = function(selector) {
- return $(selector, this.el);
- };
-
- // Cached regex to split keys for `delegate`.
- var eventSplitter = /^(\S+)\s*(.*)$/;
-
- // List of view options to be merged as properties.
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
-
- // Set up all inheritable **Backbone.View** properties and methods.
- _.extend(Backbone.View.prototype, Backbone.Events, {
-
- // The default `tagName` of a View's element is `"div"`.
- tagName : 'div',
-
- // Attach the `selectorDelegate` function as the `$` property.
- $ : selectorDelegate,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // **render** is the core function that your view should override, in order
- // to populate its element (`this.el`), with the appropriate HTML. The
- // convention is for **render** to always return `this`.
- render : function() {
- return this;
- },
-
- // Remove this view from the DOM. Note that the view isn't present in the
- // DOM by default, so calling this method may be a no-op.
- remove : function() {
- $(this.el).remove();
- return this;
- },
-
- // For small amounts of DOM Elements, where a full-blown template isn't
- // needed, use **make** to manufacture elements, one at a time.
- //
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
- //
- make : function(tagName, attributes, content) {
- var el = document.createElement(tagName);
- if (attributes) $(el).attr(attributes);
- if (content) $(el).html(content);
- return el;
- },
-
- // Set callbacks, where `this.callbacks` is a hash of
- //
- // *{"event selector": "callback"}*
- //
- // {
- // 'mousedown .title': 'edit',
- // 'click .button': 'save'
- // }
- //
- // pairs. Callbacks will be bound to the view, with `this` set properly.
- // Uses event delegation for efficiency.
- // Omitting the selector binds the event to `this.el`.
- // This only works for delegate-able events: not `focus`, `blur`, and
- // not `change`, `submit`, and `reset` in Internet Explorer.
- delegateEvents : function(events) {
- if (!(events || (events = this.events))) return;
- if (_.isFunction(events)) events = events.call(this);
- $(this.el).unbind('.delegateEvents' + this.cid);
- for (var key in events) {
- var method = this[events[key]];
- if (!method) throw new Error('Event "' + events[key] + '" does not exist');
- var match = key.match(eventSplitter);
- var eventName = match[1], selector = match[2];
- method = _.bind(method, this);
- eventName += '.delegateEvents' + this.cid;
- if (selector === '') {
- $(this.el).bind(eventName, method);
- } else {
- $(this.el).delegate(selector, eventName, method);
- }
- }
- },
-
- // Performs the initial configuration of a View with a set of options.
- // Keys with special meaning *(model, collection, id, className)*, are
- // attached directly to the view.
- _configure : function(options) {
- if (this.options) options = _.extend({}, this.options, options);
- for (var i = 0, l = viewOptions.length; i < l; i++) {
- var attr = viewOptions[i];
- if (options[attr]) this[attr] = options[attr];
- }
- this.options = options;
- },
-
- // Ensure that the View has a DOM element to render into.
- // If `this.el` is a string, pass it through `$()`, take the first
- // matching element, and re-assign it to `el`. Otherwise, create
- // an element from the `id`, `className` and `tagName` proeprties.
- _ensureElement : function() {
- if (!this.el) {
- var attrs = this.attributes || {};
- if (this.id) attrs.id = this.id;
- if (this.className) attrs['class'] = this.className;
- this.el = this.make(this.tagName, attrs);
- } else if (_.isString(this.el)) {
- this.el = $(this.el).get(0);
- }
- }
-
- });
-
- // The self-propagating extend function that Backbone classes use.
- var extend = function (protoProps, classProps) {
- var child = inherits(this, protoProps, classProps);
- child.extend = this.extend;
- return child;
- };
-
- // Set up inheritance for the model, collection, and view.
- Backbone.Model.extend = Backbone.Collection.extend =
- Backbone.Router.extend = Backbone.View.extend = extend;
-
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
- var methodMap = {
- 'create': 'POST',
- 'update': 'PUT',
- 'delete': 'DELETE',
- 'read' : 'GET'
- };
-
- // Backbone.sync
- // -------------
-
- // Override this function to change the manner in which Backbone persists
- // models to the server. You will be passed the type of request, and the
- // model in question. By default, uses makes a RESTful Ajax request
- // to the model's `url()`. Some possible customizations could be:
- //
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
- // * Send up the models as XML instead of JSON.
- // * Persist models via WebSockets instead of Ajax.
- //
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
- // as `POST`, with a `_method` parameter containing the true HTTP method,
- // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
- // `application/json` with the model in a param named `model`.
- // Useful when interfacing with server-side languages like **PHP** that make
- // it difficult to read the body of `PUT` requests.
- Backbone.sync = function(method, model, options) {
- var type = methodMap[method];
-
- // Default JSON-request options.
- var params = _.extend({
- type: type,
- dataType: 'json'
- }, options);
-
- // Ensure that we have a URL.
- if (!params.url) {
- params.url = getUrl(model) || urlError();
- }
-
- // Ensure that we have the appropriate request data.
- if (!params.data && model && (method == 'create' || method == 'update')) {
- params.contentType = 'application/json';
- params.data = JSON.stringify(model.toJSON());
- }
-
- // For older servers, emulate JSON by encoding the request into an HTML-form.
- if (Backbone.emulateJSON) {
- params.contentType = 'application/x-www-form-urlencoded';
- params.data = params.data ? {model : params.data} : {};
- }
-
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
- // And an `X-HTTP-Method-Override` header.
- if (Backbone.emulateHTTP) {
- if (type === 'PUT' || type === 'DELETE') {
- if (Backbone.emulateJSON) params.data._method = type;
- params.type = 'POST';
- params.beforeSend = function(xhr) {
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
- };
- }
- }
-
- // Don't process data on a non-GET request.
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
- params.processData = false;
- }
-
- // Make the request.
- return $.ajax(params);
- };
-
- // Helpers
- // -------
-
- // Shared empty constructor function to aid in prototype-chain creation.
- var ctor = function(){};
-
- // Helper function to correctly set up the prototype chain, for subclasses.
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
- // class properties to be extended.
- var inherits = function(parent, protoProps, staticProps) {
- var child;
-
- // The constructor function for the new subclass is either defined by you
- // (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call `super()`.
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
- child = protoProps.constructor;
- } else {
- child = function(){ return parent.apply(this, arguments); };
- }
-
- // Inherit class (static) properties from parent.
- _.extend(child, parent);
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if (protoProps) _.extend(child.prototype, protoProps);
-
- // Add static properties to the constructor function, if supplied.
- if (staticProps) _.extend(child, staticProps);
-
- // Correctly set child's `prototype.constructor`.
- child.prototype.constructor = child;
-
- // Set a convenience property in case the parent's prototype is needed later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- // Helper function to get a URL from a Model or Collection as a property
- // or as a function.
- var getUrl = function(object) {
- if (!(object && object.url)) return null;
- return _.isFunction(object.url) ? object.url() : object.url;
- };
-
- // Throw an error when a URL is needed, and none is supplied.
- var urlError = function() {
- throw new Error('A "url" property or function must be specified');
- };
-
- // Wrap an optional error callback with a fallback error event.
- var wrapError = function(onError, model, options) {
- return function(resp) {
- if (onError) {
- onError(model, resp, options);
- } else {
- model.trigger('error', model, resp, options);
- }
- };
- };
-
- // Helper function to escape a string for HTML rendering.
- var escapeHTML = function(string) {
- return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
- };
-
-}).call(this);
\ No newline at end of file
diff --git a/addons/point_of_sale/static/lib/backbone/backbone-0.9.2.js b/addons/point_of_sale/static/lib/backbone/backbone-0.9.2.js
deleted file mode 100644
index 3373c952bfa..00000000000
--- a/addons/point_of_sale/static/lib/backbone/backbone-0.9.2.js
+++ /dev/null
@@ -1,1431 +0,0 @@
-// Backbone.js 0.9.2
-
-// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://backbonejs.org
-
-(function(){
-
- // Initial Setup
- // -------------
-
- // Save a reference to the global object (`window` in the browser, `global`
- // on the server).
- var root = this;
-
- // Save the previous value of the `Backbone` variable, so that it can be
- // restored later on, if `noConflict` is used.
- var previousBackbone = root.Backbone;
-
- // Create a local reference to slice/splice.
- var slice = Array.prototype.slice;
- var splice = Array.prototype.splice;
-
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both CommonJS and the browser.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
- // Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '0.9.2';
-
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
-
- // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
- var $ = root.jQuery || root.Zepto || root.ender;
-
- // Set the JavaScript library that will be used for DOM manipulation and
- // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
- // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
- // alternate JavaScript library (or a mock library for testing your views
- // outside of a browser).
- Backbone.setDomLibrary = function(lib) {
- $ = lib;
- };
-
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
- // to its previous owner. Returns a reference to this Backbone object.
- Backbone.noConflict = function() {
- root.Backbone = previousBackbone;
- return this;
- };
-
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
- // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
- // set a `X-Http-Method-Override` header.
- Backbone.emulateHTTP = false;
-
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
- // `application/json` requests ... will encode the body as
- // `application/x-www-form-urlencoded` instead and will send the model in a
- // form param named `model`.
- Backbone.emulateJSON = false;
-
- // Backbone.Events
- // -----------------
-
- // Regular expression used to split event strings
- var eventSplitter = /\s+/;
-
- // A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may bind with `on` or remove with `off` callback functions
- // to an event; trigger`-ing an event fires all callbacks in succession.
- //
- // var object = {};
- // _.extend(object, Backbone.Events);
- // object.on('expand', function(){ alert('expanded'); });
- // object.trigger('expand');
- //
- var Events = Backbone.Events = {
-
- // Bind one or more space separated events, `events`, to a `callback`
- // function. Passing `"all"` will bind the callback to all events fired.
- on: function(events, callback, context) {
-
- var calls, event, node, tail, list;
- if (!callback) return this;
- events = events.split(eventSplitter);
- calls = this._callbacks || (this._callbacks = {});
-
- // Create an immutable callback list, allowing traversal during
- // modification. The tail is an empty object that will always be used
- // as the next node.
- while (event = events.shift()) {
- list = calls[event];
- node = list ? list.tail : {};
- node.next = tail = {};
- node.context = context;
- node.callback = callback;
- calls[event] = {tail: tail, next: list ? list.next : node};
- }
-
- return this;
- },
-
- // Remove one or many callbacks. If `context` is null, removes all callbacks
- // with that function. If `callback` is null, removes all callbacks for the
- // event. If `events` is null, removes all bound callbacks for all events.
- off: function(events, callback, context) {
- var event, calls, node, tail, cb, ctx;
-
- // No events, or removing *all* events.
- if (!(calls = this._callbacks)) return;
- if (!(events || callback || context)) {
- delete this._callbacks;
- return this;
- }
-
- // Loop through the listed events and contexts, splicing them out of the
- // linked list of callbacks if appropriate.
- events = events ? events.split(eventSplitter) : _.keys(calls);
- while (event = events.shift()) {
- node = calls[event];
- delete calls[event];
- if (!node || !(callback || context)) continue;
- // Create a new list, omitting the indicated callbacks.
- tail = node.tail;
- while ((node = node.next) !== tail) {
- cb = node.callback;
- ctx = node.context;
- if ((callback && cb !== callback) || (context && ctx !== context)) {
- this.on(event, cb, ctx);
- }
- }
- }
-
- return this;
- },
-
- // Trigger one or many events, firing all bound callbacks. Callbacks are
- // passed the same arguments as `trigger` is, apart from the event name
- // (unless you're listening on `"all"`, which will cause your callback to
- // receive the true name of the event as the first argument).
- trigger: function(events) {
- var event, node, calls, tail, args, all, rest;
- if (!(calls = this._callbacks)) return this;
- all = calls.all;
- events = events.split(eventSplitter);
- rest = slice.call(arguments, 1);
-
- // For each event, walk through the linked list of callbacks twice,
- // first to trigger the event, then to trigger any `"all"` callbacks.
- while (event = events.shift()) {
- if (node = calls[event]) {
- tail = node.tail;
- while ((node = node.next) !== tail) {
- node.callback.apply(node.context || this, rest);
- }
- }
- if (node = all) {
- tail = node.tail;
- args = [event].concat(rest);
- while ((node = node.next) !== tail) {
- node.callback.apply(node.context || this, args);
- }
- }
- }
-
- return this;
- }
-
- };
-
- // Aliases for backwards compatibility.
- Events.bind = Events.on;
- Events.unbind = Events.off;
-
- // Backbone.Model
- // --------------
-
- // Create a new model, with defined attributes. A client id (`cid`)
- // is automatically generated and assigned for you.
- var Model = Backbone.Model = function(attributes, options) {
- var defaults;
- attributes || (attributes = {});
- if (options && options.parse) attributes = this.parse(attributes);
- if (defaults = getValue(this, 'defaults')) {
- attributes = _.extend({}, defaults, attributes);
- }
- if (options && options.collection) this.collection = options.collection;
- this.attributes = {};
- this._escapedAttributes = {};
- this.cid = _.uniqueId('c');
- this.changed = {};
- this._silent = {};
- this._pending = {};
- this.set(attributes, {silent: true});
- // Reset change tracking.
- this.changed = {};
- this._silent = {};
- this._pending = {};
- this._previousAttributes = _.clone(this.attributes);
- this.initialize.apply(this, arguments);
- };
-
- // Attach all inheritable methods to the Model prototype.
- _.extend(Model.prototype, Events, {
-
- // A hash of attributes whose current and previous value differ.
- changed: null,
-
- // A hash of attributes that have silently changed since the last time
- // `change` was called. Will become pending attributes on the next call.
- _silent: null,
-
- // A hash of attributes that have changed since the last `'change'` event
- // began.
- _pending: null,
-
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
- // CouchDB users may want to set this to `"_id"`.
- idAttribute: 'id',
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // Return a copy of the model's `attributes` object.
- toJSON: function(options) {
- return _.clone(this.attributes);
- },
-
- // Get the value of an attribute.
- get: function(attr) {
- return this.attributes[attr];
- },
-
- // Get the HTML-escaped value of an attribute.
- escape: function(attr) {
- var html;
- if (html = this._escapedAttributes[attr]) return html;
- var val = this.get(attr);
- return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
- },
-
- // Returns `true` if the attribute contains a value that is not null
- // or undefined.
- has: function(attr) {
- return this.get(attr) != null;
- },
-
- // Set a hash of model attributes on the object, firing `"change"` unless
- // you choose to silence it.
- set: function(key, value, options) {
- var attrs, attr, val;
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (_.isObject(key) || key == null) {
- attrs = key;
- options = value;
- } else {
- attrs = {};
- attrs[key] = value;
- }
-
- // Extract attributes and options.
- options || (options = {});
- if (!attrs) return this;
- if (attrs instanceof Model) attrs = attrs.attributes;
- if (options.unset) for (attr in attrs) attrs[attr] = void 0;
-
- // Run validation.
- if (!this._validate(attrs, options)) return false;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
- var changes = options.changes = {};
- var now = this.attributes;
- var escaped = this._escapedAttributes;
- var prev = this._previousAttributes || {};
-
- // For each `set` attribute...
- for (attr in attrs) {
- val = attrs[attr];
-
- // If the new and current value differ, record the change.
- if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
- delete escaped[attr];
- (options.silent ? this._silent : changes)[attr] = true;
- }
-
- // Update or delete the current value.
- options.unset ? delete now[attr] : now[attr] = val;
-
- // If the new and previous value differ, record the change. If not,
- // then remove changes for this attribute.
- if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
- this.changed[attr] = val;
- if (!options.silent) this._pending[attr] = true;
- } else {
- delete this.changed[attr];
- delete this._pending[attr];
- }
- }
-
- // Fire the `"change"` events.
- if (!options.silent) this.change(options);
- return this;
- },
-
- // Remove an attribute from the model, firing `"change"` unless you choose
- // to silence it. `unset` is a noop if the attribute doesn't exist.
- unset: function(attr, options) {
- (options || (options = {})).unset = true;
- return this.set(attr, null, options);
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear: function(options) {
- (options || (options = {})).unset = true;
- return this.set(_.clone(this.attributes), options);
- },
-
- // Fetch the model from the server. If the server's representation of the
- // model differs from its current attributes, they will be overriden,
- // triggering a `"change"` event.
- fetch: function(options) {
- options = options ? _.clone(options) : {};
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp);
- };
- options.error = Backbone.wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Set a hash of model attributes, and sync the model to the server.
- // If the server returns an attributes hash that differs, the model's
- // state will be `set` again.
- save: function(key, value, options) {
- var attrs, current;
-
- // Handle both `("key", value)` and `({key: value})` -style calls.
- if (_.isObject(key) || key == null) {
- attrs = key;
- options = value;
- } else {
- attrs = {};
- attrs[key] = value;
- }
- options = options ? _.clone(options) : {};
-
- // If we're "wait"-ing to set changed attributes, validate early.
- if (options.wait) {
- if (!this._validate(attrs, options)) return false;
- current = _.clone(this.attributes);
- }
-
- // Regular saves `set` attributes before persisting to the server.
- var silentOptions = _.extend({}, options, {silent: true});
- if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
- return false;
- }
-
- // After a successful server-side save, the client is (optionally)
- // updated with the server-side state.
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- var serverAttrs = model.parse(resp, xhr);
- if (options.wait) {
- delete options.wait;
- serverAttrs = _.extend(attrs || {}, serverAttrs);
- }
- if (!model.set(serverAttrs, options)) return false;
- if (success) {
- success(model, resp);
- } else {
- model.trigger('sync', model, resp, options);
- }
- };
-
- // Finish configuring and sending the Ajax request.
- options.error = Backbone.wrapError(options.error, model, options);
- var method = this.isNew() ? 'create' : 'update';
- var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
- if (options.wait) this.set(current, silentOptions);
- return xhr;
- },
-
- // Destroy this model on the server if it was already persisted.
- // Optimistically removes the model from its collection, if it has one.
- // If `wait: true` is passed, waits for the server to respond before removal.
- destroy: function(options) {
- options = options ? _.clone(options) : {};
- var model = this;
- var success = options.success;
-
- var triggerDestroy = function() {
- model.trigger('destroy', model, model.collection, options);
- };
-
- if (this.isNew()) {
- triggerDestroy();
- return false;
- }
-
- options.success = function(resp) {
- if (options.wait) triggerDestroy();
- if (success) {
- success(model, resp);
- } else {
- model.trigger('sync', model, resp, options);
- }
- };
-
- options.error = Backbone.wrapError(options.error, model, options);
- var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
- if (!options.wait) triggerDestroy();
- return xhr;
- },
-
- // Default URL for the model's representation on the server -- if you're
- // using Backbone's restful methods, override this to change the endpoint
- // that will be called.
- url: function() {
- var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
- if (this.isNew()) return base;
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
- },
-
- // **parse** converts a response into the hash of attributes to be `set` on
- // the model. The default implementation is just to pass the response along.
- parse: function(resp, xhr) {
- return resp;
- },
-
- // Create a new model with identical attributes to this one.
- clone: function() {
- return new this.constructor(this.attributes);
- },
-
- // A model is new if it has never been saved to the server, and lacks an id.
- isNew: function() {
- return this.id == null;
- },
-
- // Call this method to manually fire a `"change"` event for this model and
- // a `"change:attribute"` event for each changed attribute.
- // Calling this will cause all objects observing the model to update.
- change: function(options) {
- options || (options = {});
- var changing = this._changing;
- this._changing = true;
-
- // Silent changes become pending changes.
- for (var attr in this._silent) this._pending[attr] = true;
-
- // Silent changes are triggered.
- var changes = _.extend({}, options.changes, this._silent);
- this._silent = {};
- for (var attr in changes) {
- this.trigger('change:' + attr, this, this.get(attr), options);
- }
- if (changing) return this;
-
- // Continue firing `"change"` events while there are pending changes.
- while (!_.isEmpty(this._pending)) {
- this._pending = {};
- this.trigger('change', this, options);
- // Pending and silent changes still remain.
- for (var attr in this.changed) {
- if (this._pending[attr] || this._silent[attr]) continue;
- delete this.changed[attr];
- }
- this._previousAttributes = _.clone(this.attributes);
- }
-
- this._changing = false;
- return this;
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged: function(attr) {
- if (!arguments.length) return !_.isEmpty(this.changed);
- return _.has(this.changed, attr);
- },
-
- // Return an object containing all the attributes that have changed, or
- // false if there are no changed attributes. Useful for determining what
- // parts of a view need to be updated and/or what attributes need to be
- // persisted to the server. Unset attributes will be set to undefined.
- // You can also pass an attributes object to diff against the model,
- // determining if there *would be* a change.
- changedAttributes: function(diff) {
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
- var val, changed = false, old = this._previousAttributes;
- for (var attr in diff) {
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
- (changed || (changed = {}))[attr] = val;
- }
- return changed;
- },
-
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous: function(attr) {
- if (!arguments.length || !this._previousAttributes) return null;
- return this._previousAttributes[attr];
- },
-
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes: function() {
- return _.clone(this._previousAttributes);
- },
-
- // Check if the model is currently in a valid state. It's only possible to
- // get into an *invalid* state if you're using silent changes.
- isValid: function() {
- return !this.validate(this.attributes);
- },
-
- // Run validation against the next complete set of model attributes,
- // returning `true` if all is well. If a specific `error` callback has
- // been passed, call that instead of firing the general `"error"` event.
- _validate: function(attrs, options) {
- if (options.silent || !this.validate) return true;
- attrs = _.extend({}, this.attributes, attrs);
- var error = this.validate(attrs, options);
- if (!error) return true;
- if (options && options.error) {
- options.error(this, error, options);
- } else {
- this.trigger('error', this, error, options);
- }
- return false;
- }
-
- });
-
- // Backbone.Collection
- // -------------------
-
- // Provides a standard collection class for our sets of models, ordered
- // or unordered. If a `comparator` is specified, the Collection will maintain
- // its models in sort order, as they're added and removed.
- var Collection = Backbone.Collection = function(models, options) {
- options || (options = {});
- if (options.model) this.model = options.model;
- if (options.comparator) this.comparator = options.comparator;
- this._reset();
- this.initialize.apply(this, arguments);
- if (models) this.reset(models, {silent: true, parse: options.parse});
- };
-
- // Define the Collection's inheritable methods.
- _.extend(Collection.prototype, Events, {
-
- // The default model for a collection is just a **Backbone.Model**.
- // This should be overridden in most cases.
- model: Model,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // The JSON representation of a Collection is an array of the
- // models' attributes.
- toJSON: function(options) {
- return this.map(function(model){ return model.toJSON(options); });
- },
-
- // Add a model, or list of models to the set. Pass **silent** to avoid
- // firing the `add` event for every new model.
- add: function(models, options) {
- var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
- options || (options = {});
- models = _.isArray(models) ? models.slice() : [models];
-
- // Begin by turning bare objects into model references, and preventing
- // invalid models or duplicate models from being added.
- for (i = 0, length = models.length; i < length; i++) {
- if (!(model = models[i] = this._prepareModel(models[i], options))) {
- throw new Error("Can't add an invalid model to a collection");
- }
- cid = model.cid;
- id = model.id;
- if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
- dups.push(i);
- continue;
- }
- cids[cid] = ids[id] = model;
- }
-
- // Remove duplicates.
- i = dups.length;
- while (i--) {
- models.splice(dups[i], 1);
- }
-
- // Listen to added models' events, and index models for lookup by
- // `id` and by `cid`.
- for (i = 0, length = models.length; i < length; i++) {
- (model = models[i]).on('all', this._onModelEvent, this);
- this._byCid[model.cid] = model;
- if (model.id != null) this._byId[model.id] = model;
- }
-
- // Insert models into the collection, re-sorting if needed, and triggering
- // `add` events unless silenced.
- this.length += length;
- index = options.at != null ? options.at : this.models.length;
- splice.apply(this.models, [index, 0].concat(models));
- if (this.comparator) this.sort({silent: true});
- if (options.silent) return this;
- for (i = 0, length = this.models.length; i < length; i++) {
- if (!cids[(model = this.models[i]).cid]) continue;
- options.index = i;
- model.trigger('add', model, this, options);
- }
- return this;
- },
-
- // Remove a model, or a list of models from the set. Pass silent to avoid
- // firing the `remove` event for every model removed.
- remove: function(models, options) {
- var i, l, index, model;
- options || (options = {});
- models = _.isArray(models) ? models.slice() : [models];
- for (i = 0, l = models.length; i < l; i++) {
- model = this.getByCid(models[i]) || this.get(models[i]);
- if (!model) continue;
- delete this._byId[model.id];
- delete this._byCid[model.cid];
- index = this.indexOf(model);
- this.models.splice(index, 1);
- this.length--;
- if (!options.silent) {
- options.index = index;
- model.trigger('remove', model, this, options);
- }
- this._removeReference(model);
- }
- return this;
- },
-
- // Add a model to the end of the collection.
- push: function(model, options) {
- model = this._prepareModel(model, options);
- this.add(model, options);
- return model;
- },
-
- // Remove a model from the end of the collection.
- pop: function(options) {
- var model = this.at(this.length - 1);
- this.remove(model, options);
- return model;
- },
-
- // Add a model to the beginning of the collection.
- unshift: function(model, options) {
- model = this._prepareModel(model, options);
- this.add(model, _.extend({at: 0}, options));
- return model;
- },
-
- // Remove a model from the beginning of the collection.
- shift: function(options) {
- var model = this.at(0);
- this.remove(model, options);
- return model;
- },
-
- // Get a model from the set by id.
- get: function(id) {
- if (id == null) return void 0;
- return this._byId[id.id != null ? id.id : id];
- },
-
- // Get a model from the set by client id.
- getByCid: function(cid) {
- return cid && this._byCid[cid.cid || cid];
- },
-
- // Get the model at the given index.
- at: function(index) {
- return this.models[index];
- },
-
- // Return models with matching attributes. Useful for simple cases of `filter`.
- where: function(attrs) {
- if (_.isEmpty(attrs)) return [];
- return this.filter(function(model) {
- for (var key in attrs) {
- if (attrs[key] !== model.get(key)) return false;
- }
- return true;
- });
- },
-
- // Force the collection to re-sort itself. You don't need to call this under
- // normal circumstances, as the set will maintain sort order as each item
- // is added.
- sort: function(options) {
- options || (options = {});
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- var boundComparator = _.bind(this.comparator, this);
- if (this.comparator.length == 1) {
- this.models = this.sortBy(boundComparator);
- } else {
- this.models.sort(boundComparator);
- }
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Pluck an attribute from each model in the collection.
- pluck: function(attr) {
- return _.map(this.models, function(model){ return model.get(attr); });
- },
-
- // When you have more items than you want to add or remove individually,
- // you can reset the entire set with a new list of models, without firing
- // any `add` or `remove` events. Fires `reset` when finished.
- reset: function(models, options) {
- models || (models = []);
- options || (options = {});
- for (var i = 0, l = this.models.length; i < l; i++) {
- this._removeReference(this.models[i]);
- }
- this._reset();
- this.add(models, _.extend({silent: true}, options));
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Fetch the default set of models for this collection, resetting the
- // collection when they arrive. If `add: true` is passed, appends the
- // models to the collection instead of resetting.
- fetch: function(options) {
- options = options ? _.clone(options) : {};
- if (options.parse === undefined) options.parse = true;
- var collection = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
- if (success) success(collection, resp);
- };
- options.error = Backbone.wrapError(options.error, collection, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Create a new instance of a model in this collection. Add the model to the
- // collection immediately, unless `wait: true` is passed, in which case we
- // wait for the server to agree.
- create: function(model, options) {
- var coll = this;
- options = options ? _.clone(options) : {};
- model = this._prepareModel(model, options);
- if (!model) return false;
- if (!options.wait) coll.add(model, options);
- var success = options.success;
- options.success = function(nextModel, resp, xhr) {
- if (options.wait) coll.add(nextModel, options);
- if (success) {
- success(nextModel, resp);
- } else {
- nextModel.trigger('sync', model, resp, options);
- }
- };
- model.save(null, options);
- return model;
- },
-
- // **parse** converts a response into a list of models to be added to the
- // collection. The default implementation is just to pass it through.
- parse: function(resp, xhr) {
- return resp;
- },
-
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
- // underscore methods are proxied because it relies on the underscore
- // constructor.
- chain: function () {
- return _(this.models).chain();
- },
-
- // Reset all internal state. Called when the collection is reset.
- _reset: function(options) {
- this.length = 0;
- this.models = [];
- this._byId = {};
- this._byCid = {};
- },
-
- // Prepare a model or hash of attributes to be added to this collection.
- _prepareModel: function(model, options) {
- options || (options = {});
- if (!(model instanceof Model)) {
- var attrs = model;
- options.collection = this;
- model = new this.model(attrs, options);
- if (!model._validate(model.attributes, options)) model = false;
- } else if (!model.collection) {
- model.collection = this;
- }
- return model;
- },
-
- // Internal method to remove a model's ties to a collection.
- _removeReference: function(model) {
- if (this == model.collection) {
- delete model.collection;
- }
- model.off('all', this._onModelEvent, this);
- },
-
- // Internal method called every time a model in the set fires an event.
- // Sets need to update their indexes when models change ids. All other
- // events simply proxy through. "add" and "remove" events that originate
- // in other collections are ignored.
- _onModelEvent: function(event, model, collection, options) {
- if ((event == 'add' || event == 'remove') && collection != this) return;
- if (event == 'destroy') {
- this.remove(model, options);
- }
- if (model && event === 'change:' + model.idAttribute) {
- delete this._byId[model.previous(model.idAttribute)];
- this._byId[model.id] = model;
- }
- this.trigger.apply(this, arguments);
- }
-
- });
-
- // Underscore methods that we want to implement on the Collection.
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
- 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
- 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
- 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
- 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
-
- // Mix in each Underscore method as a proxy to `Collection#models`.
- _.each(methods, function(method) {
- Collection.prototype[method] = function() {
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
- };
- });
-
- // Backbone.Router
- // -------------------
-
- // Routers map faux-URLs to actions, and fire events when routes are
- // matched. Creating a new one sets its `routes` hash, if not set statically.
- var Router = Backbone.Router = function(options) {
- options || (options = {});
- if (options.routes) this.routes = options.routes;
- this._bindRoutes();
- this.initialize.apply(this, arguments);
- };
-
- // Cached regular expressions for matching named param parts and splatted
- // parts of route strings.
- var namedParam = /:\w+/g;
- var splatParam = /\*\w+/g;
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
-
- // Set up all inheritable **Backbone.Router** properties and methods.
- _.extend(Router.prototype, Events, {
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // Manually bind a single named route to a callback. For example:
- //
- // this.route('search/:query/p:num', 'search', function(query, num) {
- // ...
- // });
- //
- route: function(route, name, callback) {
- Backbone.history || (Backbone.history = new History);
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
- if (!callback) callback = this[name];
- Backbone.history.route(route, _.bind(function(fragment) {
- var args = this._extractParameters(route, fragment);
- callback && callback.apply(this, args);
- this.trigger.apply(this, ['route:' + name].concat(args));
- Backbone.history.trigger('route', this, name, args);
- }, this));
- return this;
- },
-
- // Simple proxy to `Backbone.history` to save a fragment into the history.
- navigate: function(fragment, options) {
- Backbone.history.navigate(fragment, options);
- },
-
- // Bind all defined routes to `Backbone.history`. We have to reverse the
- // order of the routes here to support behavior where the most general
- // routes can be defined at the bottom of the route map.
- _bindRoutes: function() {
- if (!this.routes) return;
- var routes = [];
- for (var route in this.routes) {
- routes.unshift([route, this.routes[route]]);
- }
- for (var i = 0, l = routes.length; i < l; i++) {
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
- }
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp: function(route) {
- route = route.replace(escapeRegExp, '\\$&')
- .replace(namedParam, '([^\/]+)')
- .replace(splatParam, '(.*?)');
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted parameters.
- _extractParameters: function(route, fragment) {
- return route.exec(fragment).slice(1);
- }
-
- });
-
- // Backbone.History
- // ----------------
-
- // Handles cross-browser history management, based on URL fragments. If the
- // browser does not support `onhashchange`, falls back to polling.
- var History = Backbone.History = function() {
- this.handlers = [];
- _.bindAll(this, 'checkUrl');
- };
-
- // Cached regex for cleaning leading hashes and slashes .
- var routeStripper = /^[#\/]/;
-
- // Cached regex for detecting MSIE.
- var isExplorer = /msie [\w.]+/;
-
- // Has the history handling already been started?
- History.started = false;
-
- // Set up all inheritable **Backbone.History** properties and methods.
- _.extend(History.prototype, Events, {
-
- // The default interval to poll for hash changes, if necessary, is
- // twenty times a second.
- interval: 50,
-
- // Gets the true hash value. Cannot use location.hash directly due to bug
- // in Firefox where location.hash will always be decoded.
- getHash: function(windowOverride) {
- var loc = windowOverride ? windowOverride.location : window.location;
- var match = loc.href.match(/#(.*)$/);
- return match ? match[1] : '';
- },
-
- // Get the cross-browser normalized URL fragment, either from the URL,
- // the hash, or the override.
- getFragment: function(fragment, forcePushState) {
- if (fragment == null) {
- if (this._hasPushState || forcePushState) {
- fragment = window.location.pathname;
- var search = window.location.search;
- if (search) fragment += search;
- } else {
- fragment = this.getHash();
- }
- }
- if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
- return fragment.replace(routeStripper, '');
- },
-
- // Start the hash change handling, returning `true` if the current URL matches
- // an existing route, and `false` otherwise.
- start: function(options) {
- if (History.started) throw new Error("Backbone.history has already been started");
- History.started = true;
-
- // Figure out the initial configuration. Do we need an iframe?
- // Is pushState desired ... is it available?
- this.options = _.extend({}, {root: '/'}, this.options, options);
- this._wantsHashChange = this.options.hashChange !== false;
- this._wantsPushState = !!this.options.pushState;
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
- var fragment = this.getFragment();
- var docMode = document.documentMode;
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
-
- if (oldIE) {
- this.iframe = $('').hide().appendTo('body')[0].contentWindow;
- this.navigate(fragment);
- }
-
- // Depending on whether we're using pushState or hashes, and whether
- // 'onhashchange' is supported, determine how we check the URL state.
- if (this._hasPushState) {
- $(window).bind('popstate', this.checkUrl);
- } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
- $(window).bind('hashchange', this.checkUrl);
- } else if (this._wantsHashChange) {
- this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
- }
-
- // Determine if we need to change the base url, for a pushState link
- // opened by a non-pushState browser.
- this.fragment = fragment;
- var loc = window.location;
- var atRoot = loc.pathname == this.options.root;
-
- // If we've started off with a route from a `pushState`-enabled browser,
- // but we're currently in a browser that doesn't support it...
- if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
- this.fragment = this.getFragment(null, true);
- window.location.replace(this.options.root + '#' + this.fragment);
- // Return immediately as browser will do redirect to new url
- return true;
-
- // Or if we've started out with a hash-based route, but we're currently
- // in a browser where it could be `pushState`-based instead...
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
- this.fragment = this.getHash().replace(routeStripper, '');
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
- }
-
- if (!this.options.silent) {
- return this.loadUrl();
- }
- },
-
- // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
- // but possibly useful for unit testing Routers.
- stop: function() {
- $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
- clearInterval(this._checkUrlInterval);
- History.started = false;
- },
-
- // Add a route to be tested when the fragment changes. Routes added later
- // may override previous routes.
- route: function(route, callback) {
- this.handlers.unshift({route: route, callback: callback});
- },
-
- // Checks the current URL to see if it has changed, and if it has,
- // calls `loadUrl`, normalizing across the hidden iframe.
- checkUrl: function(e) {
- var current = this.getFragment();
- if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
- if (current == this.fragment) return false;
- if (this.iframe) this.navigate(current);
- this.loadUrl() || this.loadUrl(this.getHash());
- },
-
- // Attempt to load the current URL fragment. If a route succeeds with a
- // match, returns `true`. If no defined routes matches the fragment,
- // returns `false`.
- loadUrl: function(fragmentOverride) {
- var fragment = this.fragment = this.getFragment(fragmentOverride);
- var matched = _.any(this.handlers, function(handler) {
- if (handler.route.test(fragment)) {
- handler.callback(fragment);
- return true;
- }
- });
- return matched;
- },
-
- // Save a fragment into the hash history, or replace the URL state if the
- // 'replace' option is passed. You are responsible for properly URL-encoding
- // the fragment in advance.
- //
- // The options object can contain `trigger: true` if you wish to have the
- // route callback be fired (not usually desirable), or `replace: true`, if
- // you wish to modify the current URL without adding an entry to the history.
- navigate: function(fragment, options) {
- if (!History.started) return false;
- if (!options || options === true) options = {trigger: options};
- var frag = (fragment || '').replace(routeStripper, '');
- if (this.fragment == frag) return;
-
- // If pushState is available, we use it to set the fragment as a real URL.
- if (this._hasPushState) {
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
- this.fragment = frag;
- window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
-
- // If hash changes haven't been explicitly disabled, update the hash
- // fragment to store history.
- } else if (this._wantsHashChange) {
- this.fragment = frag;
- this._updateHash(window.location, frag, options.replace);
- if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
- // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
- // When replace is true, we don't want this.
- if(!options.replace) this.iframe.document.open().close();
- this._updateHash(this.iframe.location, frag, options.replace);
- }
-
- // If you've told us that you explicitly don't want fallback hashchange-
- // based history, then `navigate` becomes a page refresh.
- } else {
- window.location.assign(this.options.root + fragment);
- }
- if (options.trigger) this.loadUrl(fragment);
- },
-
- // Update the hash location, either replacing the current entry, or adding
- // a new one to the browser history.
- _updateHash: function(location, fragment, replace) {
- if (replace) {
- location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
- } else {
- location.hash = fragment;
- }
- }
- });
-
- // Backbone.View
- // -------------
-
- // Creating a Backbone.View creates its initial element outside of the DOM,
- // if an existing element is not provided...
- var View = Backbone.View = function(options) {
- this.cid = _.uniqueId('view');
- this._configure(options || {});
- this._ensureElement();
- this.initialize.apply(this, arguments);
- this.delegateEvents();
- };
-
- // Cached regex to split keys for `delegate`.
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
-
- // List of view options to be merged as properties.
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
-
- // Set up all inheritable **Backbone.View** properties and methods.
- _.extend(View.prototype, Events, {
-
- // The default `tagName` of a View's element is `"div"`.
- tagName: 'div',
-
- // jQuery delegate for element lookup, scoped to DOM elements within the
- // current view. This should be prefered to global lookups where possible.
- $: function(selector) {
- return this.$el.find(selector);
- },
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize: function(){},
-
- // **render** is the core function that your view should override, in order
- // to populate its element (`this.el`), with the appropriate HTML. The
- // convention is for **render** to always return `this`.
- render: function() {
- return this;
- },
-
- // Remove this view from the DOM. Note that the view isn't present in the
- // DOM by default, so calling this method may be a no-op.
- remove: function() {
- this.$el.remove();
- return this;
- },
-
- // For small amounts of DOM Elements, where a full-blown template isn't
- // needed, use **make** to manufacture elements, one at a time.
- //
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
- //
- make: function(tagName, attributes, content) {
- var el = document.createElement(tagName);
- if (attributes) $(el).attr(attributes);
- if (content) $(el).html(content);
- return el;
- },
-
- // Change the view's element (`this.el` property), including event
- // re-delegation.
- setElement: function(element, delegate) {
- if (this.$el) this.undelegateEvents();
- this.$el = (element instanceof $) ? element : $(element);
- this.el = this.$el[0];
- if (delegate !== false) this.delegateEvents();
- return this;
- },
-
- // Set callbacks, where `this.events` is a hash of
- //
- // *{"event selector": "callback"}*
- //
- // {
- // 'mousedown .title': 'edit',
- // 'click .button': 'save'
- // 'click .open': function(e) { ... }
- // }
- //
- // pairs. Callbacks will be bound to the view, with `this` set properly.
- // Uses event delegation for efficiency.
- // Omitting the selector binds the event to `this.el`.
- // This only works for delegate-able events: not `focus`, `blur`, and
- // not `change`, `submit`, and `reset` in Internet Explorer.
- delegateEvents: function(events) {
- if (!(events || (events = getValue(this, 'events')))) return;
- this.undelegateEvents();
- for (var key in events) {
- var method = events[key];
- if (!_.isFunction(method)) method = this[events[key]];
- if (!method) throw new Error('Method "' + events[key] + '" does not exist');
- var match = key.match(delegateEventSplitter);
- var eventName = match[1], selector = match[2];
- method = _.bind(method, this);
- eventName += '.delegateEvents' + this.cid;
- if (selector === '') {
- this.$el.bind(eventName, method);
- } else {
- this.$el.delegate(selector, eventName, method);
- }
- }
- },
-
- // Clears all callbacks previously bound to the view with `delegateEvents`.
- // You usually don't need to use this, but may wish to if you have multiple
- // Backbone views attached to the same DOM element.
- undelegateEvents: function() {
- this.$el.unbind('.delegateEvents' + this.cid);
- },
-
- // Performs the initial configuration of a View with a set of options.
- // Keys with special meaning *(model, collection, id, className)*, are
- // attached directly to the view.
- _configure: function(options) {
- if (this.options) options = _.extend({}, this.options, options);
- for (var i = 0, l = viewOptions.length; i < l; i++) {
- var attr = viewOptions[i];
- if (options[attr]) this[attr] = options[attr];
- }
- this.options = options;
- },
-
- // Ensure that the View has a DOM element to render into.
- // If `this.el` is a string, pass it through `$()`, take the first
- // matching element, and re-assign it to `el`. Otherwise, create
- // an element from the `id`, `className` and `tagName` properties.
- _ensureElement: function() {
- if (!this.el) {
- var attrs = getValue(this, 'attributes') || {};
- if (this.id) attrs.id = this.id;
- if (this.className) attrs['class'] = this.className;
- this.setElement(this.make(this.tagName, attrs), false);
- } else {
- this.setElement(this.el, false);
- }
- }
-
- });
-
- // The self-propagating extend function that Backbone classes use.
- var extend = function (protoProps, classProps) {
- var child = inherits(this, protoProps, classProps);
- child.extend = this.extend;
- return child;
- };
-
- // Set up inheritance for the model, collection, and view.
- Model.extend = Collection.extend = Router.extend = View.extend = extend;
-
- // Backbone.sync
- // -------------
-
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
- var methodMap = {
- 'create': 'POST',
- 'update': 'PUT',
- 'delete': 'DELETE',
- 'read': 'GET'
- };
-
- // Override this function to change the manner in which Backbone persists
- // models to the server. You will be passed the type of request, and the
- // model in question. By default, makes a RESTful Ajax request
- // to the model's `url()`. Some possible customizations could be:
- //
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
- // * Send up the models as XML instead of JSON.
- // * Persist models via WebSockets instead of Ajax.
- //
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
- // as `POST`, with a `_method` parameter containing the true HTTP method,
- // as well as all requests with the body as `application/x-www-form-urlencoded`
- // instead of `application/json` with the model in a param named `model`.
- // Useful when interfacing with server-side languages like **PHP** that make
- // it difficult to read the body of `PUT` requests.
- Backbone.sync = function(method, model, options) {
- var type = methodMap[method];
-
- // Default options, unless specified.
- options || (options = {});
-
- // Default JSON-request options.
- var params = {type: type, dataType: 'json'};
-
- // Ensure that we have a URL.
- if (!options.url) {
- params.url = getValue(model, 'url') || urlError();
- }
-
- // Ensure that we have the appropriate request data.
- if (!options.data && model && (method == 'create' || method == 'update')) {
- params.contentType = 'application/json';
- params.data = JSON.stringify(model.toJSON());
- }
-
- // For older servers, emulate JSON by encoding the request into an HTML-form.
- if (Backbone.emulateJSON) {
- params.contentType = 'application/x-www-form-urlencoded';
- params.data = params.data ? {model: params.data} : {};
- }
-
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
- // And an `X-HTTP-Method-Override` header.
- if (Backbone.emulateHTTP) {
- if (type === 'PUT' || type === 'DELETE') {
- if (Backbone.emulateJSON) params.data._method = type;
- params.type = 'POST';
- params.beforeSend = function(xhr) {
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
- };
- }
- }
-
- // Don't process data on a non-GET request.
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
- params.processData = false;
- }
-
- // Make the request, allowing the user to override any Ajax options.
- return $.ajax(_.extend(params, options));
- };
-
- // Wrap an optional error callback with a fallback error event.
- Backbone.wrapError = function(onError, originalModel, options) {
- return function(model, resp) {
- resp = model === originalModel ? resp : model;
- if (onError) {
- onError(originalModel, resp, options);
- } else {
- originalModel.trigger('error', originalModel, resp, options);
- }
- };
- };
-
- // Helpers
- // -------
-
- // Shared empty constructor function to aid in prototype-chain creation.
- var ctor = function(){};
-
- // Helper function to correctly set up the prototype chain, for subclasses.
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
- // class properties to be extended.
- var inherits = function(parent, protoProps, staticProps) {
- var child;
-
- // The constructor function for the new subclass is either defined by you
- // (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call the parent's constructor.
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
- child = protoProps.constructor;
- } else {
- child = function(){ parent.apply(this, arguments); };
- }
-
- // Inherit class (static) properties from parent.
- _.extend(child, parent);
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if (protoProps) _.extend(child.prototype, protoProps);
-
- // Add static properties to the constructor function, if supplied.
- if (staticProps) _.extend(child, staticProps);
-
- // Correctly set child's `prototype.constructor`.
- child.prototype.constructor = child;
-
- // Set a convenience property in case the parent's prototype is needed later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- // Helper function to get a value from a Backbone object as a property
- // or as a function.
- var getValue = function(object, prop) {
- if (!(object && object[prop])) return null;
- return _.isFunction(object[prop]) ? object[prop]() : object[prop];
- };
-
- // Throw an error when a URL is needed, and none is supplied.
- var urlError = function() {
- throw new Error('A "url" property or function must be specified');
- };
-
-}).call(this);
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 114e6ccee9e..a9baf041942 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -31,6 +31,7 @@
background: -ms-linear-gradient(top,rgba(0,0,0,0.09),rgba(0,0,0,0));
background: linear-gradient(top,rgba(0,0,0,0.09),rgba(0,0,0,0));
}
+
.point-of-sale .darker-shadow-top{
position: absolute;
top:0;
@@ -42,6 +43,7 @@
background: -ms-linear-gradient(top,rgba(0,0,0,0.15),rgba(0,0,0,0));
background: linear-gradient(top,rgba(0,0,0,0.15),rgba(0,0,0,0));
}
+
/* ********* The black loading screen ********* */
.point-of-sale .loader{
@@ -156,11 +158,11 @@
}
.point-of-sale #rightheader button {
- color: black;
- height:29px;
- margin:2px;
+ color: #273072;
+ height:27px;
+ margin:3px;
margin-right:0px;
- border: 1px solid black;
+ border: 1px solid #353A7E;
background: #7f82ac;
background: -webkit-gradient(linear, left top, left bottom, from(#b2b3d7), to(#7f82ac));
background: -moz-linear-gradient(#b2b3d7, #7f82ac);
@@ -187,6 +189,33 @@
font-weight: 900;
}
+/* c) The session buttons */
+
+.point-of-sale #rightheader .header-button{
+ float:right;
+ height:32px;
+ padding-left:10px;
+ padding-right:10px;
+ border-right: 1px solid #3a3a3a;
+ border-left: 1px solid #3a3a3a;
+ color:#DDD;
+ line-height:32px;
+ text-align:center;
+ cursor: pointer;
+
+ -webkit-transition-property: background;
+ -webkit-transition-duration: 0.2s;
+ -webkit-transition-timing-function: ease-out;
+}
+.point-of-sale #rightheader .header-button:last-child{
+ border-left: 1px solid #3a3a3a;
+}
+.point-of-sale #rightheader .header-button:hover{
+ background: rgba(0,0,0,0.2);
+ text-shadow: #000 0px 0px 3px;
+ color:#EEE;
+}
+
/* c) The notifications indicator */
.point-of-sale .oe_pos_synch-notification{
@@ -234,7 +263,8 @@
left:0;
width:440px;
top:0px;
- bottom:105px;
+ /*bottom:105px;*/
+ bottom:0;
border-right: solid 1px #CECBCB;
background-color: white;
}
@@ -318,7 +348,8 @@
.point-of-sale #rightpane {
position: absolute;
top: 0;
- bottom: 105px;
+ /*bottom: 105px;*/
+ bottom:0;
left: 440px;
right: 0;
vertical-align: top;
@@ -454,6 +485,42 @@
cursor: pointer;
}
+.point-of-sale .category-simple-button{
+ position: relative;
+ display: inline-block;
+ font-size: 14px;
+ margin-right:10px;
+ padding:5px;
+
+ cursor: pointer;
+ border: 1px solid #cacaca;
+ border-radius: 4px;
+
+ background: #e2e2e2;
+ background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
+ background: linear-gradient(#f0f0f0, #e2e2e2);
+ -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+}
+.point-of-sale .category-simple-button:hover {
+ color: white;
+ background: #7f82ac;
+ border: 1px solid #7f82ac;
+ background: -webkit-linear-gradient(#9d9fc5, #7f82ac);
+ background: -moz-linear-gradient(#9d9fc5, #7f82ac);
+ background: -ms-linear-gradient(#9d9fc5, #7f82ac);
+ background: linear-gradient(#9d9fc5, #7f82ac);
+
+ -webkit-transition-property: background, border;
+ -webkit-transition-duration: 0.2s;
+ -webkit-transition-timing-function: ease-out;
+}
+
+
+
.point-of-sale .category-button .category-img {
position: relative;
width: 120px;
@@ -463,7 +530,8 @@
}
.point-of-sale .category-button .category-img img {
- height: 100px;
+ max-height: 100px;
+ max-width: 120px;
}
.point-of-sale .category-button .category-name {
@@ -490,6 +558,7 @@
position:relative;
vertical-align: top;
display: inline-block;
+ line-height: 100px;
font-size: 11px;
margin: 5px;
width: 120px;
@@ -497,9 +566,9 @@
background:#fff;
border: 1px solid #fff;
border-radius: 3px;
- -webkit-box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
- -moz-box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
- box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
+ -webkit-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
+ -moz-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
+ box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
}
.point-of-sale .product .product-img {
@@ -511,7 +580,8 @@
}
.point-of-sale .product .product-img img {
- height: 100px;
+ max-height: 100px;
+ max-width: 120px;
}
.point-of-sale .product .price-tag {
@@ -520,6 +590,7 @@
right: 2px;
vertical-align: top;
color: white;
+ line-height: 14px;
background: #7f82ac;
padding: 2px 5px;
border-radius: 3px;
@@ -533,6 +604,7 @@
box-sizing: border-box;
bottom:0;
top:auto;
+ line-height: 14px;
width:100%;
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
@@ -598,8 +670,16 @@
display: inline-block;
font-size: 1.5em;
}
+.point-of-sale .greyed-out{
+ color: #AAA;
+}
+.point-of-sale .pos-step-container input{
+ font-size: 1em;
+}
+
.point-of-sale .pos-payment-container {
text-align: left;
+ min-width: 500px;
}
.point-of-sale .pos-payment-container .left-block{
display: inline-block;
@@ -648,8 +728,17 @@
width: 300px;
background-color: white;
margin: 20px;
- padding: 10px;
+ padding: 15px;
+ padding-bottom:30px;
display: inline-block;
+ font-family: "Inconsolata";
+ -webkit-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
+ -moz-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
+ box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
+}
+.point-of-sale .pos-sale-ticket .emph{
+ font-size: 20px;
+ margin:5px;
}
.point-of-sale .pos-sale-ticket table {
width: 100%;
@@ -660,18 +749,18 @@
}
@media print {
- #oe_header, #oe_menu, .point-of-sale #topheader, .point-of-sale #leftpane {
- display: none;
+ .point-of-sale #topheader, .point-of-sale #leftpane {
+ display: none !important;
}
.point-of-sale #content {
- top: 0px;
+ top: 0px !important;
}
.point-of-sale #rightpane {
- left: 0px;
+ left: 0px !important;
background-color: white;
}
#receipt-screen header {
- display: none;
+ display: none !important;
}
#receipt-screen {
text-align: left;
@@ -761,6 +850,28 @@
font-family: "Inconsolata";
}
+/* e) The Welcome Screen */
+.point-of-sale .goodbye-message{
+ position: absolute;
+ left:50%;
+ top:30%;
+ width:500px;
+ height:400px;
+ margin-left: -250px;
+ margin-top: -200px;
+ padding:10px;
+ padding-top:20px;
+ text-align:center;
+ font-size:20px;
+ font-weight:bold;
+ background-color: #F0EEEE;
+ border: 1px solid #E0DDDD;
+ -webkit-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
+ -moz-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
+ -ms-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
+ z-index:1150;
+}
+
/* ********* The OrderWidget ********* */
.point-of-sale .order-container{
@@ -846,6 +957,10 @@
-moz-transition: background 50ms ease-in-out;
transition: background 50ms ease-in-out;
}
+.point-of-sale .order .orderline.empty:hover{
+ background: transparent;
+ cursor: default;
+}
.point-of-sale .order .orderline.selected{
background: rgba(140,143,183,0.2);
@@ -933,9 +1048,9 @@
background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
background: linear-gradient(#f0f0f0, #e2e2e2);
- -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
- -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
- box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
+ -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
}
.point-of-sale .pos-actionbar .button .label{
margin-top: 37px;
@@ -957,6 +1072,23 @@
-webkit-transition-timing-function: ease-out;
}
+.point-of-sale .pos-actionbar .button.disabled{
+ color:#AAA;
+}
+.point-of-sale .pos-actionbar .button.disabled:hover{
+ border: 1px solid #cacaca;
+ border-radius: 4px;
+
+ background: #e2e2e2;
+ background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
+ background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
+ background: linear-gradient(#f0f0f0, #e2e2e2);
+ -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+ box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
+}
+
.point-of-sale .pos-actionbar .button.rightalign{
float:right;
}
diff --git a/addons/point_of_sale/static/src/img/default_category_photo.png b/addons/point_of_sale/static/src/img/default_category_photo.png
new file mode 100644
index 00000000000..25af75ee815
Binary files /dev/null and b/addons/point_of_sale/static/src/img/default_category_photo.png differ
diff --git a/addons/point_of_sale/static/src/img/icons/bancontact.svg b/addons/point_of_sale/static/src/img/icons/bancontact.svg
new file mode 100644
index 00000000000..70604078014
--- /dev/null
+++ b/addons/point_of_sale/static/src/img/icons/bancontact.svg
@@ -0,0 +1,1114 @@
+
+
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/img/icons/scan.svg b/addons/point_of_sale/static/src/img/icons/scan.svg
new file mode 100644
index 00000000000..4d753acf638
--- /dev/null
+++ b/addons/point_of_sale/static/src/img/icons/scan.svg
@@ -0,0 +1,984 @@
+
+
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/js/TODO.txt b/addons/point_of_sale/static/src/js/TODO.txt
new file mode 100644
index 00000000000..edadbe188ea
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/TODO.txt
@@ -0,0 +1,62 @@
+v Affichage Catégories doivent respecter le poids
+v Affichage Catégories par poids doivent avoir une hiérarchie plate
+v Le Onscreen keyboard ne fonctionne plus
+v Scrolling dans la sélection produit et dans
+ la liste de courses
+v Réductions
+v Redesign liste de courses
+v bugs scans produits par prix par poids
+v si pas photo photo par defaut
+v WebSQL
+v Redesign header, bouton retour aux backend en mode caissière
+v bouton exit en mode caissière doit retourner au backend.
+v posting orders
+v bug ajout ligne payment à zero + curseur dedans + suppression + design plus grand
+v si case print via proxy cochée, alors on skip l'ecran receipt
+v bouton exit self checkout
+v demarrage en mode self-checkout si self-checkout
+v produits poid code barre non reconnus
+v activer popup client aux écrans self-checkout
+v discount pas plus grand que 100
+v numpad dans l'écran payment pour pouvoir entrer le montant
+v différence de total. Methode d'arrondis
+
+- login alternatif
+- user preferences : désactiver la balance etc.
+- numpad state parfois sans state
+- numpad state en mode pesée ?? le cacher ?
+- Redesign du receipt
+- retirer le receipt screen
+- le numpad en mode paymenet marche pas
+- l'écran de payement est hyper laid
+- l'écran de pesée est hyper laid
+x générer les données de printing
+x popups d'erreur certainement buggés
+
+TODO AUG 20
+-----------
+
+* GREEN STATUS
+ - make it green !
+
+* TRUNK
+ - Connection status tooltip
+ v Remove receipt screen
+ - Finish the receipt JSON generation
+ - Modifie le widget de liste course
+ v supprimer l'écran
+ v bloquer sur l'impression
+ v L'impression est foireuse
+
+* CLIENT
+ - create a new branch
+ - Self-checkout welcome screen
+ - removal of products for the root category
+ - Terminal de payement
+ - Code à barres
+ - Vidanges
+ - Design
+ - Backport
+ - CSS pour écran résistif
+ - Test sur matos
+
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
new file mode 100644
index 00000000000..77f7307b48f
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -0,0 +1,235 @@
+function openerp_pos_db(instance, module){
+
+ /* PosLS is a LocalStorage based implementation of the point of sale database,
+ it performs better for few products, but does not scale beyond 500 products.
+ */
+ module.PosLS = instance.web.Class.extend({
+ name: 'openerp_pos_ls', //the prefix of the localstorage data
+ limit: 100, // the maximum number of results returned by a search
+ init: function(options){
+ options = options || {};
+ this.name = options.name || this.name;
+ this.limit = options.limit || this.limit;
+
+ //cache the data in memory to avoid roundtrips to the localstorage
+ this.cache = {};
+
+ this.category_by_id = {};
+ this.root_category_id = 0;
+ this.category_products = {};
+ this.category_ancestors = {};
+ this.category_childs = {};
+ this.category_parent = {};
+ this.category_search_string = {};
+ },
+ /* returns the category object from its id. If you pass a list of id as parameters, you get
+ * a list of category objects.
+ */
+ get_category_by_id: function(categ_id){
+ if(categ_id instanceof Array){
+ var list = [];
+ for(var i = 0, len = categ_id.length; i < len; i++){
+ var cat = this.category_by_id[categ_id[i]];
+ if(cat){
+ list.push(cat);
+ }else{
+ console.error("get_category_by_id: no category has id:",categ_id[i]);
+ }
+ }
+ return list;
+ }else{
+ return this.category_by_id[categ_id];
+ }
+ },
+ /* returns a list of the category's child categories ids, or an empty list
+ * if a category has no childs */
+ get_category_childs_ids: function(categ_id){
+ return this.category_childs[categ_id] || [];
+ },
+ /* returns a list of all ancestors (parent, grand-parent, etc) categories ids
+ * starting from the root category to the direct parent */
+ get_category_ancestors_ids: function(categ_id){
+ return this.category_ancestors[categ_id] || [];
+ },
+ /* returns the parent category's id of a category, or the root_category_id if no parent.
+ * the root category is parent of itself. */
+ get_category_parent_id: function(categ_id){
+ return this.category_parent[categ_id] || this.root_category_id;
+ },
+ /* adds categories definitions to the database. categories is a list of categories objects as
+ * returned by the openerp server. Categories must be inserted before the products or the
+ * product/ categories association may (will) not work properly */
+ add_categories: function(categories){
+ var self = this;
+ if(!this.category_by_id[this.root_category_id]){
+ this.category_by_id[this.root_category_id] = {
+ id : this.root_category_id,
+ name : 'Root',
+ };
+ }
+ for(var i=0, len = categories.length; i < len; i++){
+ this.category_by_id[categories[i].id] = categories[i];
+ }
+ for(var i=0, len = categories.length; i < len; i++){
+ var cat = categories[i];
+ var parent_id = cat.parent_id[0] || this.root_category_id;
+ this.category_parent[cat.id] = cat.parent_id[0];
+ if(!this.category_childs[parent_id]){
+ this.category_childs[parent_id] = [];
+ }
+ this.category_childs[parent_id].push(cat.id);
+ }
+ function make_ancestors(cat_id, ancestors){
+ self.category_ancestors[cat_id] = ancestors;
+
+ ancestors = ancestors.slice(0);
+ ancestors.push(cat_id);
+
+ var childs = self.category_childs[cat_id] || [];
+ for(var i=0, len = childs.length; i < len; i++){
+ make_ancestors(childs[i], ancestors);
+ }
+ }
+ make_ancestors(this.root_category_id, []);
+ },
+ /* loads a record store from the database. returns default if nothing is found */
+ load: function(store,deft){
+ if(this.cache[store] !== undefined){
+ return this.cache[store];
+ }
+ var data = localStorage[this.name + '_' + store];
+ if(data !== undefined){
+ data = JSON.parse(data);
+ this.cache[store] = data;
+ return data;
+ }else{
+ return deft;
+ }
+ },
+ /* saves a record store to the database */
+ save: function(store,data){
+ localStorage[this.name + '_' + store] = JSON.stringify(data);
+ this.cache[store] = data;
+ },
+ _product_search_string: function(product){
+ var str = '' + product.id + ':' + product.name;
+ if(product.ean13){
+ str += '|' + product.ean13;
+ }
+ return str + '\n';
+ },
+ add_products: function(products){
+ var stored_products = this.load('products',{});
+ var stored_categories = this.load('categories',{});
+
+ if(!products instanceof Array){
+ products = [products];
+ }
+ for(var i = 0, len = products.length; i < len; i++){
+ var product = products[i];
+ var search_string = this._product_search_string(product);
+ var categ_id = product.pos_categ_id[0];
+ if(!stored_categories[categ_id]){
+ stored_categories[categ_id] = [];
+ }
+ stored_categories[categ_id].push(product.id);
+
+ if(this.category_search_string[categ_id] === undefined){
+ this.category_search_string[categ_id] = '';
+ }
+ this.category_search_string[categ_id] += search_string;
+
+ var ancestors = this.get_category_ancestors_ids(categ_id) || [];
+
+ for(var j = 0; j < ancestors.length; j++){
+ var ancestor = ancestors[j];
+ if(! stored_categories[ancestor]){
+ stored_categories[ancestor] = [];
+ }
+ stored_categories[ancestor].push(product.id);
+
+ if( this.category_search_string[ancestor] === undefined){
+ this.category_search_string[ancestor] = '';
+ }
+ this.category_search_string[ancestor] += search_string;
+ }
+ stored_products[product.id] = product;
+ }
+ this.save('products',stored_products);
+ this.save('categories',stored_categories);
+ },
+ /* removes all the data from the database. TODO : being able to selectively remove data */
+ clear: function(stores){
+ for(var i = 0, len = arguments.length; i < len; i++){
+ localStorage.removeItem(this.name + '_' + arguments[i]);
+ }
+ },
+ /* this internal methods returns the count of properties in an object. */
+ _count_props : function(obj){
+ var count = 0;
+ for(var prop in obj){
+ if(obj.hasOwnProperty(prop)){
+ count++;
+ }
+ }
+ return count;
+ },
+ get_product_by_id: function(id){
+ return this.load('products',{})[id];
+ },
+ get_product_by_ean13: function(ean13){
+ var products = this.load('products',{});
+ for(var i in products){
+ if( products[i] && products[i].ean13 === ean13){
+ return products[i];
+ }
+ }
+ return undefined;
+ },
+ get_product_by_category: function(category_id){
+ var stored_categories = this.load('categories',{});
+ var stored_products = this.load('products',{});
+ var product_ids = stored_categories[category_id];
+ var list = [];
+ for(var i = 0, len = Math.min(product_ids.length,this.limit); i < len; i++){
+ list.push(stored_products[product_ids[i]]);
+ }
+ return list;
+ },
+ /* returns a list of products with :
+ * - a category that is or is a child of category_id,
+ * - a name, package or ean13 containing the query (case insensitive)
+ */
+ search_product_in_category: function(category_id, query){
+ var re = RegExp("([0-9]+):.*?"+query,"gi");
+ var results = [];
+ for(var i = 0; i < this.limit; i++){
+ r = re.exec(this.category_search_string[category_id]);
+ if(r){
+ var id = Number(r[1]);
+ results.push(this.get_product_by_id(id));
+ }else{
+ break;
+ }
+ }
+ return results;
+ },
+ add_order: function(order){
+ var last_id = this.load('last_order_id',0);
+ var orders = this.load('orders',[]);
+ orders.push({id: last_id + 1, data: order});
+ this.save('last_order_id',last_id+1);
+ this.save('orders',orders);
+ },
+ remove_order: function(order_id){
+ var orders = this.load('orders',[]);
+ orders = _.filter(orders, function(order){
+ return order.id !== order_id;
+ });
+ this.save('orders',orders);
+ },
+ get_orders: function(){
+ return this.load('orders',[]);
+ },
+ });
+}
diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/devices.js
similarity index 82%
rename from addons/point_of_sale/static/src/js/pos_devices.js
rename to addons/point_of_sale/static/src/js/devices.js
index 2857fc39937..a6be16b3835 100644
--- a/addons/point_of_sale/static/src/js/pos_devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -17,7 +17,9 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
delay_payment: function(){ this.activate(); this.payment_status = 'waiting_for_payment'; },
}))();
- //window.debug_devices = debug_devices;
+ if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
+ window.debug_devices = debug_devices;
+ }
// this object interfaces with the local proxy to communicate to the various hardware devices
// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
@@ -42,21 +44,26 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
success_callback = success_callback || function(){};
error_callback = error_callback || function(){};
- if(debug_devices && debug_devices.active){
+
+ if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
console.log('PROXY:',name,params);
- }else{
+ }
+
+ if(!(debug_devices && debug_devices.active)){
this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback);
}
},
//a product has been scanned and recognized with success
- scan_item_success: function(){
- this.message('scan_item_success');
+ // ean is a parsed ean object
+ scan_item_success: function(ean){
+ this.message('scan_item_success',ean);
},
- //a product has been scanned but not recognized
- scan_item_error_unrecognized: function(){
- this.message('scan_item_error_unrecognized');
+ // a product has been scanned but not recognized
+ // ean is a parsed ean object
+ scan_item_error_unrecognized: function(ean){
+ this.message('scan_item_error_unrecognized',ean);
},
//the client is asking for help
@@ -222,11 +229,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback_stack = [];
- this.price_prefix_set = attributes.price_prefix_set || {'02':'', '22':'', '24':'', '26':'', '28':''};
- this.weight_prefix_set = attributes.weight_prefix_set || {'21':'','23':'','27':'','29':'','25':''};
- this.client_prefix_set = attributes.weight_prefix_set || {'42':''};
- this.cashier_prefix_set = attributes.weight_prefix_set || {'40':''};
- this.discount_prefix_set = attributes.weight_prefix_set || {'44':''};
+ this.weight_prefix_set = attributes.weight_prefix_set || {'21':''};
+ this.discount_prefix_set = attributes.discount_prefix_set || {'22':''};
+ this.price_prefix_set = attributes.price_prefix_set || {'23':''};
+ this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''};
+ this.client_prefix_set = attributes.client_prefix_set || {'042':''};
},
save_callbacks: function(){
var callbacks = {};
@@ -270,41 +277,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback[action] = undefined;
}
},
-
-
+ // returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string
+ ean_checksum: function(ean){
+ var code = ean.split('');
+ if(code.length !== 13){
+ return -1;
+ }
+ var oddsum = 0, evensum = 0, total = 0;
+ code = code.reverse().splice(1);
+ for(var i = 0; i < code.length; i++){
+ if(i % 2 == 0){
+ oddsum += Number(code[i]);
+ }else{
+ evensum += Number(code[i]);
+ }
+ }
+ total = oddsum * 3 + evensum;
+ return Number((10 - total % 10) % 10);
+ },
// returns true if the ean is a valid EAN codebar number by checking the control digit.
// ean must be a string
check_ean: function(ean){
- var code = ean.split('');
- for(var i = 0; i < code.length; i++){
- code[i] = Number(code[i]);
+ return this.ean_checksum(ean) === Number(ean[ean.length-1]);
+ },
+ // returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
+ sanitize_ean:function(ean){
+ ean = ean.substr(0,13);
+
+ for(var n = 0, count = (13 - ean.length); n < count; n++){
+ ean = ean + '0';
}
- var st1 = code.slice();
- var st2 = st1.slice(0,st1.length-1).reverse();
- // some EAN13 barcodes have a length of 12, as they start by 0
- while (st2.length < 12) {
- st2.push(0);
- }
- var countSt3 = 1;
- var st3 = 0;
- $.each(st2, function() {
- if (countSt3%2 === 1) {
- st3 += this;
- }
- countSt3 ++;
- });
- st3 *= 3;
- var st4 = 0;
- var countSt4 = 1;
- $.each(st2, function() {
- if (countSt4%2 === 0) {
- st4 += this;
- }
- countSt4 ++;
- });
- var st5 = st3 + st4;
- var cd = (10 - (st5%10)) % 10;
- return code[code.length-1] === cd;
+ return ean.substr(0,12) + this.ean_checksum(ean);
},
// attempts to interpret an ean (string encoding an ean)
@@ -327,41 +330,47 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
type:'unknown', //
prefix:'',
ean:ean,
+ base_ean: ean,
id:'',
value: 0,
unit: 'none',
};
- var prefix2 = ean.substring(0,2);
+ console.log('ean',ean);
- if(!this.check_ean(ean)){
+ function match_prefix(prefix_set, type){
+ for(prefix in prefix_set){
+ if(ean.substring(0,prefix.length) === prefix){
+ parse_result.prefix = prefix;
+ parse_result.type = type;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (!this.check_ean(ean)){
parse_result.type = 'error';
- }else if (prefix2 in this.price_prefix_set){
- parse_result.type = 'price';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.price_prefix_set,'price')){
parse_result.id = ean.substring(0,7);
+ parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.value = Number(ean.substring(7,12))/100.0;
parse_result.unit = 'euro';
- } else if (prefix2 in this.weight_prefix_set){
- parse_result.type = 'weight';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.weight_prefix_set,'weight')){
parse_result.id = ean.substring(0,7);
parse_result.value = Number(ean.substring(7,12))/1000.0;
+ parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.unit = 'Kg';
- }else if (prefix2 in this.client_prefix_set){
- parse_result.type = 'client';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.client_prefix_set,'client')){
parse_result.id = ean.substring(0,7);
- }else if (prefix2 in this.cashier_prefix_set){
- parse_result.type = 'cashier';
- parse_result.prefix = prefix2;
+ parse_result.unit = 'Kg';
+ } else if( match_prefix(this.cashier_prefix_set,'cashier')){
parse_result.id = ean.substring(0,7);
- }else if (prefix2 in this.discount_prefix_set){
- parse_result.type = 'discount';
- parse_result.prefix = prefix2;
+ } else if( match_prefix(this.discount_prefix_set,'discount')){
parse_result.id = ean.substring(0,7);
+ parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.value = Number(ean.substring(7,12))/100.0;
parse_result.unit = '%';
- }else{
+ } else {
parse_result.type = 'unit';
parse_result.prefix = '';
parse_result.id = ean;
@@ -402,7 +411,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
var parse_result = self.parse_ean(codeNumbers.join(''));
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
- console.error('ERROR: barcode checksum error:',parse_result);
+ console.warn('WARNING: barcode checksum error:',parse_result);
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
if(self.action_callback['product']){
self.action_callback['product'](parse_result);
diff --git a/addons/point_of_sale/static/src/js/pos_main.js b/addons/point_of_sale/static/src/js/main.js
similarity index 92%
rename from addons/point_of_sale/static/src/js/pos_main.js
rename to addons/point_of_sale/static/src/js/main.js
index 71ac69c6067..b9263b91314 100644
--- a/addons/point_of_sale/static/src/js/pos_main.js
+++ b/addons/point_of_sale/static/src/js/main.js
@@ -5,6 +5,8 @@ openerp.point_of_sale = function(instance) {
var module = instance.point_of_sale;
+ openerp_pos_db(instance,module); // import db.js
+
openerp_pos_models(instance,module); // import pos_models.js
openerp_pos_basewidget(instance,module); // import pos_basewidget.js
diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/models.js
similarity index 58%
rename from addons/point_of_sale/static/src/js/pos_models.js
rename to addons/point_of_sale/static/src/js/models.js
index 8a1506769bd..a1f6def6bf3 100644
--- a/addons/point_of_sale/static/src/js/pos_models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1,51 +1,6 @@
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
- module.LocalStorageDAO = instance.web.Class.extend({
- add_operation: function(operation) {
- var self = this;
- return $.async_when().pipe(function() {
- var tmp = self._get('oe_pos_operations', []);
- var last_id = self._get('oe_pos_operations_sequence', 1);
- tmp.push({'id': last_id, 'data': operation});
- self._set('oe_pos_operations', tmp);
- self._set('oe_pos_operations_sequence', last_id + 1);
- });
- },
- remove_operation: function(id) {
- var self = this;
- return $.async_when().pipe(function() {
- var tmp = self._get('oe_pos_operations', []);
- tmp = _.filter(tmp, function(el) {
- return el.id !== id;
- });
- self._set('oe_pos_operations', tmp);
- });
- },
- get_operations: function() {
- var self = this;
- return $.async_when().pipe(function() {
- return self._get('oe_pos_operations', []);
- });
- },
- _get: function(key, default_) {
- var txt = localStorage['oe_pos_dao_'+key];
- if (! txt)
- return default_;
- return JSON.parse(txt);
- },
- _set: function(key, value) {
- localStorage['oe_pos_dao_'+key] = JSON.stringify(value);
- },
- reset_stored_data: function(){
- for(key in localStorage){
- if(key.indexOf('oe_pos_dao_') === 0){
- delete localStorage[key];
- }
- }
- },
- });
-
var fetch = function(model, fields, domain, ctx){
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
};
@@ -58,29 +13,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// this is done asynchronously, a ready deferred alows the GUI to wait interactively
// for the loading to be completed
// There is a single instance of the PosModel for each Front-End instance, it is usually called
- // 'pos' and is available to almost all widgets.
+ // 'pos' and is available to all widgets extending PosWidget.
module.PosModel = Backbone.Model.extend({
initialize: function(session, attributes) {
Backbone.Model.prototype.initialize.call(this, attributes);
var self = this;
- this.dao = new module.LocalStorageDAO(); // used to store the order's data on the Hard Drive
+ this.session = session;
this.ready = $.Deferred(); // used to notify the GUI that the PosModel has loaded all resources
this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
- //this.build_tree = _.bind(this.build_tree, this); // ???
- this.session = session;
- this.categories = {};
- this.root_category = null;
- this.weightable_categories = []; // a flat list of all categories that directly contain weightable products
- this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
- this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
- // pos settings
- this.use_scale = false;
- this.use_proxy_printer = false;
- this.use_virtual_keyboard = false;
- this.use_websql = false;
- this.use_barcode_scanner = false;
+ this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
+ this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
+ this.db = new module.PosLS(); // a database used to store the products and categories
+ this.db.clear('products','categories');
// default attributes values. If null, it will be loaded below.
this.set({
@@ -98,12 +44,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'products': new module.ProductCollection(),
'cashRegisters': null,
- 'product_list': null, // the list of all products, does not change.
'bank_statements': null,
'taxes': null,
'pos_session': null,
'pos_config': null,
- 'categories': null,
+ 'units': null,
+ 'units_by_id': null,
'selectedOrder': undefined,
});
@@ -113,10 +59,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
- var user_def = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
+ var loaded = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
.pipe(function(users){
- var user = users[0];
- self.set('user',user);
+ self.set('user',users[0]);
return fetch('res.company',
[
@@ -124,171 +69,114 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'email',
'website',
'company_registry',
- //TODO contact_address
'vat',
'name',
- 'phone'
+ 'phone',
+ 'partner_id',
],
- [['id','=',user.company_id[0]]])
+ [['id','=',users[0].company_id[0]]]);
}).pipe(function(companies){
- var company = companies[0];
- self.set('company',company);
+ self.set('company',companies[0]);
- return fetch('res.currency',['symbol','position'],[['id','=',company.currency_id[0]]]);
- }).pipe(function (currencies){
+ return fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]);
+ }).pipe(function(company_partners){
+ self.get('company').contact_address = company_partners[0].contact_address;
+
+ return fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]);
+ }).pipe(function(currencies){
self.set('currency',currencies[0]);
- });
- var cat_def = fetch('pos.category', ['id','name', 'parent_id', 'child_id', 'image_medium'])
- .pipe(function(result){
- return self.set({'categories': result});
- });
-
- var uom_def = fetch( //unit of measure
- 'product.uom',
- null,
- null
- ).then(function(result){
- self.set({'units': result});
+ return fetch('product.uom', null, null);
+ }).pipe(function(units){
+ self.set('units',units);
var units_by_id = {};
- for(var i = 0, len = result.length; i < len; i++){
- units_by_id[result[i].id] = result[i];
+ for(var i = 0, len = units.length; i < len; i++){
+ units_by_id[units[i].id] = units[i];
}
- self.set({'units_by_id':units_by_id});
- });
-
- var pack_def = fetch(
- 'product.packaging',
- null,
- null
- ).then(function(packaging){
- self.set('product.packaging',packaging);
- });
-
- var users_def = fetch(
- 'res.users',
- ['name','ean13'],
- [['ean13', '!=', false]]
- ).then(function(result){
- self.set({'user_list':result});
- });
-
- var tax_def = fetch('account.tax', ['amount','price_include','type'])
- .then(function(result){
- self.set({'taxes': result});
- });
-
- var session_def = fetch( // loading the PoS Session.
- 'pos.session',
- ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
- [['state', '=', 'opened'], ['user_id', '=', this.session.uid]]
- ).pipe(function(result) {
-
- // some data are associated with the pos session, like the pos config and bank statements.
- // we must have a valid session before we can read those.
+ self.set('units_by_id',units_by_id);
- var session_data_def = new $.Deferred();
+ return fetch('product.packaging', null, null);
+ }).pipe(function(packagings){
+ self.set('product.packaging',packagings);
- if( result.length !== 0 ) {
- var pos_session = result[0];
+ return fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
+ }).pipe(function(users){
+ self.set('user_list',users);
- self.set({'pos_session': pos_session});
+ return fetch('account.tax', ['amount', 'price_include', 'type']);
+ }).pipe(function(taxes){
+ self.set('taxes', taxes);
- var pos_config_def = fetch(
- 'pos.config',
- ['name','journal_ids','shop_id','journal_id',
- 'iface_self_checkout', 'iface_websql', 'iface_led', 'iface_cashdrawer',
- 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
- 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
- [['id','=', pos_session.config_id[0]]]
- ).pipe(function(result){
- var pos_config = result[0]
-
- self.set({'pos_config': pos_config});
- self.use_scale = pos_config.iface_electronic_scale || false;
- self.use_proxy_printer = pos_config.iface_print_via_proxy || false;
- self.use_virtual_keyboard = pos_config.iface_vkeyboard || false;
- self.use_websql = pos_config.iface_websql || false;
- self.use_barcode_scanner = pos_config.iface_barscan || false;
- self.use_selfcheckout = pos_config.iface_self_checkout || false;
- self.use_cashbox = pos_config.iface_cashdrawer || false;
+ return fetch(
+ 'pos.session',
+ ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
+ [['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
+ );
+ }).pipe(function(sessions){
+ self.set('pos_session', sessions[0]);
- return shop_def = fetch('sale.shop',[], [['id','=',pos_config.shop_id[0]]])
- }).pipe(function(shops){
- self.set('shop',shops[0]);
- return fetch(
- 'product.product',
- //context {pricelist: shop.pricelist_id[0]}
- ['name', 'list_price','price','pos_categ_id', 'taxes_id','image_medium', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
- [['pos_categ_id','!=', false]],
- {pricelist: shops[0].pricelist_id[0]} // context for price
- );
- }).pipe( function(product_list){
- self.set({'product_list': product_list});
- });
+ return fetch(
+ 'pos.config',
+ ['name','journal_ids','shop_id','journal_id',
+ 'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
+ 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
+ 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
+ [['id','=', self.get('pos_session').config_id[0]]]
+ );
+ }).pipe(function(configs){
+ var pos_config = configs[0];
+ self.set('pos_config', pos_config);
+ self.iface_electronic_scale = !!pos_config.iface_electronic_scale;
+ self.iface_print_via_proxy = !!pos_config.iface_print_via_proxy;
+ self.iface_vkeyboard = !!pos_config.iface_vkeyboard;
+ self.iface_self_checkout = !!pos_config.iface_self_checkout;
+ self.iface_cashdrawer = !!pos_config.iface_cashdrawer;
- var bank_def = fetch(
- 'account.bank.statement',
- ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
- [['state','=','open'],['pos_session_id', '=', pos_session.id]]
- ).then(function(result){
- self.set({'bank_statements':result});
- });
+ return fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]);
+ }).pipe(function(shops){
+ self.set('shop',shops[0]);
- var journal_def = fetch(
- 'account.journal',
- undefined,
- [['user_id','=',pos_session.user_id[0]]]
- ).then(function(result){
- self.set({'journals':result});
- });
+ return fetch('pos.category', ['id','name','parent_id','child_id','image'])
+ }).pipe(function(categories){
+ self.db.add_categories(categories);
- // associate the bank statements with their journals.
- var bank_process_def = $.when(bank_def, journal_def)
- .then(function(){
- var bank_statements = self.get('bank_statements');
- var journals = self.get('journals');
- for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
- for(var j = 0, jlen = journals.length; j < jlen; j++){
- if(bank_statements[i].journal_id[0] === journals[j].id){
- bank_statements[i].journal = journals[j];
- bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
- }
- }
- }
- });
+ return fetch(
+ 'product.product',
+ ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
+ [['pos_categ_id','!=', false]],
+ {pricelist: self.get('shop').pricelist_id[0]} // context for price
+ );
+ }).pipe(function(products){
+ self.db.add_products(products);
- session_data_def = $.when(pos_config_def,bank_def,journal_def,bank_process_def);
+ return fetch(
+ 'account.bank.statement',
+ ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
+ [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]]
+ );
+ }).pipe(function(bank_statements){
+ self.set('bank_statements', bank_statements);
- }else{
- session_data_def.reject();
- }
- return session_data_def;
- });
-
- // associate the products with their categories
- var prod_process_def = $.when(cat_def, session_def)
- .pipe(function(){
- var product_list = self.get('product_list');
- var categories = self.get('categories');
- var cat_by_id = {};
- for(var i = 0; i < categories.length; i++){
- cat_by_id[categories[i].id] = categories[i];
- }
- //set the parent in the category
- for(var i = 0; i < categories.length; i++){
- categories[i].parent_category = cat_by_id[categories[i].parent_id[0]];
- }
- for(var i = 0; i < product_list.length; i++){
- product_list[i].pos_category = cat_by_id[product_list[i].pos_categ_id[0]];
+ return fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]);
+ }).pipe(function(journals){
+ self.set('journals',journals);
+
+ // associate the bank statements with their journals.
+ var bank_statements = self.get('bank_statements');
+ for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
+ for(var j = 0, jlen = journals.length; j < jlen; j++){
+ if(bank_statements[i].journal_id[0] === journals[j].id){
+ bank_statements[i].journal = journals[j];
+ bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
+ }
+ }
}
+ self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
});
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
- $.when(pack_def, cat_def, user_def, users_def, uom_def, session_def, tax_def, prod_process_def, user_def, this.flush())
+ $.when(loaded)
.then(function(){
- self.build_categories();
- self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
//self.log_loaded_data(); //Uncomment if you want to log the data to the console for easier debugging
self.ready.resolve();
},function(){
@@ -302,7 +190,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
log_loaded_data: function(){
console.log('PosModel data has been loaded:');
console.log('PosModel: categories:',this.get('categories'));
- console.log('PosModel: product_list:',this.get('product_list'));
console.log('PosModel: units:',this.get('units'));
console.log('PosModel: bank_statements:',this.get('bank_statements'));
console.log('PosModel: journals:',this.get('journals'));
@@ -316,7 +203,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
console.log('PosModel: user_list:',this.get('user_list'));
console.log('PosModel: user:',this.get('user'));
console.log('PosModel.session:',this.session);
- console.log('PosModel.categories:',this.categories);
console.log('PosModel end of data log.');
},
@@ -324,7 +210,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// order and a valid selected order
on_removed_order: function(removed_order){
if( this.get('orders').isEmpty()){
- this.add_and_select_order(new module.Order({ pos: this }));
+ this.add_new_order();
}
if( this.get('selectedOrder') === removed_order){
this.set({ selectedOrder: this.get('orders').last() });
@@ -333,175 +219,70 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
push_order: function(record) {
- var self = this;
- return this.dao.add_operation(record).pipe(function(){
- return self.flush();
- });
+ this.db.add_order(record);
+ this.flush();
},
- add_and_select_order: function(newOrder) {
- (this.get('orders')).add(newOrder);
- return this.set({
- selectedOrder: newOrder
- });
+ //creates a new empty order and sets it as the current order
+ add_new_order: function(){
+ var order = new module.Order({pos:this});
+ this.get('orders').add(order);
+ this.set('selectedOrder', order);
},
-
- // attemps to send all pending orders ( stored in the DAO ) to the server.
- // it will do it one by one, and remove the successfully sent ones from the DAO once
- // it has been confirmed that they have been received.
+
+ // attemps to send all pending orders ( stored in the pos_db ) to the server,
+ // and remove the successfully sent ones from the db once
+ // it has been confirmed that they have been sent correctly.
flush: function() {
//this makes sure only one _int_flush is called at the same time
return this.flush_mutex.exec(_.bind(function() {
- return this._int_flush();
+ return this._flush(0);
}, this));
},
- _int_flush : function() {
+ // attempts to send an order of index 'index' in the list of order to send. The index
+ // is used to skip orders that failed. do not call this method outside the mutex provided
+ // by flush()
+ _flush: function(index){
var self = this;
+ var orders = this.db.get_orders();
+ self.set('nbr_pending_operations',orders.length);
- this.dao.get_operations().pipe(function(operations) {
- // operations are really Orders that are converted to json.
- // they are saved to disk and then we attempt to send them to the backend so that they can
- // be applied.
- // since the network is not reliable we potentially have many 'pending operations' that have not been sent.
- self.set( {'nbr_pending_operations':operations.length} );
- if(operations.length === 0){
- return $.when();
- }
- var order = operations[0];
-
- // we prevent the default error handler and assume errors
- // are a normal use case, except we stop the current iteration
-
- return (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
- .fail(function(unused, event){
- // wtf ask niv
- event.preventDefault();
- })
- .pipe(function(){
- // success: remove the successfully sent operation, and try to send the next one
- self.dao.remove_operation(operations[0].id).pipe(function(){
- return self._int_flush();
- });
- }, function(){
- // in case of error we just sit there and do nothing. wtf ask niv
- return $.when();
- });
- });
+ var order = orders[index];
+ if(!order){
+ return;
+ }
+ //try to push an order to the server
+ (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
+ .fail(function(unused, event){
+ //don't show error popup if it fails
+ event.preventDefault();
+ console.error('Failed to send order:',order);
+ self._flush(index+1);
+ })
+ .done(function(){
+ //remove from db if success
+ self.db.remove_order(order.id);
+ self._flush(index);
+ });
},
- // this adds several properties to the categories in order to make it easier to diplay them
- // fields added include the list of product relevant to each category, list of child categories,
- // list of ancestors, etc.
- build_categories : function(){
- var categories = this.get('categories');
- var products = this.get('product_list');
+ scan_product: function(parsed_ean){
+ var self = this;
+ var product = this.db.get_product_by_ean13(parsed_ean.base_ean);
+ var selectedOrder = this.get('selectedOrder');
- //append the content of array2 into array1
- function append(array1, array2){
- for(var i = 0, len = array2.length; i < len; i++){
- array1.push(array2[i]);
- }
+ if(!product){
+ return false;
}
- function appendSet(set1, set2){
- for(key in set2){
- set1[key] = set2[key];
- }
+ if(parsed_ean.type === 'price'){
+ selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
+ }else if(parsed_ean.type === 'weight'){
+ selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
+ }else{
+ selectedOrder.addProduct(new module.Product(product));
}
-
- var categories_by_id = {};
- for(var i = 0; i < categories.length; i++){
- categories_by_id[categories[i].id] = categories[i];
- }
- this.categories_by_id = categories_by_id;
-
- var root_category = {
- name : 'Root',
- id : 0,
- parent : null,
- childrens : [],
- };
-
- // add parent and childrens field to categories, find root_categories
- for(var i = 0; i < categories.length; i++){
- var cat = categories[i];
-
- cat.parent = categories_by_id[cat.parent_id[0]];
- if(!cat.parent){
- root_category.childrens.push(cat);
- cat.parent = root_category;
- }
-
- cat.childrens = [];
- for(var j = 0; j < cat.child_id.length; j++){
- cat.childrens.push(categories_by_id[ cat.child_id[j] ]);
- }
- }
-
- categories.push(root_category);
-
- // set some default fields for next steps
- for(var i = 0; i < categories.length; i++){
- var cat = categories[i];
-
- cat.product_list = []; //list of all products in the category
- cat.product_set = {}; // [product.id] === true if product is in category
- cat.weightable_product_list = [];
- cat.weightable_product_set = {};
- cat.weightable = false; //true if directly contains weightable products
- }
-
- this.root_category = root_category;
-
- //we add the products to the categories.
- for(var i = 0, len = products.length; i < len; i++){
- var product = products[i];
- var cat = categories_by_id[product.pos_categ_id[0]];
- if(cat){
- cat.product_list.push(product);
- cat.product_set[product.id] = true;
- if(product.to_weight){
- cat.weightable_product_list.push(product);
- cat.weightable_product_set[product.id] = true;
- cat.weightable = true;
- }
- }
- }
-
- // we build a flat list of all categories that directly contains weightable products
- this.weightable_categories = [];
- for(var i = 0, len = categories.length; i < len; i++){
- var cat = categories[i];
- if(cat.weightable){
- this.weightable_categories.push(cat);
- }
- }
-
- // add ancestor field to categories, contains the list of parents of parents, from root to parent
- function make_ancestors(cat, ancestors){
- cat.ancestors = ancestors.slice(0);
- ancestors.push(cat);
-
- for(var i = 0; i < cat.childrens.length; i++){
- make_ancestors(cat.childrens[i], ancestors.slice(0));
- }
- }
-
- //add the products of the subcategories to the parent categories
- function make_products(cat){
- for(var i = 0; i < cat.childrens.length; i++){
- make_products(cat.childrens[i]);
-
- append(cat.product_list, cat.childrens[i].product_list);
- append(cat.weightable_product_list, cat.childrens[i].weightable_product_list);
-
- appendSet(cat.product_set, cat.childrens[i].product_set);
- appendSet(cat.weightable_product_set, cat.childrens[i].weightable_product_set);
- }
- }
-
- make_ancestors(root_category,[]);
- make_products(root_category);
+ return true;
},
});
@@ -608,15 +389,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return false;
}else if(this.get_discount() > 0){ // we don't merge discounted orderlines
return false;
- }else if(this.get_product_type() === 'unit'){
- return true;
- }else if(this.get_product_type() === 'weight'){
- return true;
- }else if(this.get_product_type() === 'price'){
- return this.get_product().get('list_price') === orderline.get_product().get('list_price');
- }else{
- console.error('point_of_sale/pos_models.js/Orderline.can_be_merged_with() : unknown product type:',this.get('product_type'));
+ }else if(this.price !== orderline.price){
return false;
+ }else{
+ return true;
}
},
merge: function(orderline){
@@ -882,6 +658,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
change: this.getChange(),
name : this.getName(),
client: client ? client.name : null ,
+ invoice_id: null, //TODO
cashier: cashier ? cashier.name : null,
date: {
year: date.getFullYear(),
@@ -895,7 +672,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
email: company.email,
website: company.website,
company_registry: company.company_registry,
- contact_address: null, //TODO
+ contact_address: company.contact_address,
vat: company.vat,
name: company.name,
phone: company.phone,
diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/screens.js
similarity index 84%
rename from addons/point_of_sale/static/src/js/pos_screens.js
rename to addons/point_of_sale/static/src/js/screens.js
index 0515eb9807b..738eadb08c5 100644
--- a/addons/point_of_sale/static/src/js/pos_screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -152,14 +152,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.screen_selector.show_popup('help');
},
- logout_button_action: function(){
- this.pos_widget.screen_selector.set_user_mode('client');
- },
-
- close_button_action: function(){
- this.pos_widget.try_close();
- },
-
barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
barcode_product_error_popup: 'error', //if defined, this popup will be loaded when there's an error in the popup
@@ -167,14 +159,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if
// there's an error.
barcode_product_action: function(ean){
- if(this.pos_widget.scan_product(ean)){
- this.pos.proxy.scan_item_success();
- if(this.barcode_product_screen){
- this.pos_widget.screen_selector.set_current_screen(this.barcode_product_screen);
+ var self = this;
+ if(self.pos.scan_product(ean)){
+ self.pos.proxy.scan_item_success(ean);
+ if(self.barcode_product_screen){
+ self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
}else{
- if(this.barcode_product_error_popup){
- this.pos_widget.screen_selector.show_popup(this.barcode_product_error_popup);
+ self.pos.proxy.scan_item_error_unrecognized(ean);
+ if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
+ self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
}
}
},
@@ -194,6 +188,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
return true;
}
}
+ this.pos.proxy.scan_item_unrecognized(ean);
return false;
},
@@ -207,9 +202,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
if(users[i].ean13 === ean.ean){
this.pos.get('selectedOrder').set_client(users[i]);
this.pos_widget.username.refresh();
+ this.pos.proxy.scan_item_success(ean);
return true;
}
}
+ this.pos.proxy.scan_item_unrecognized(ean);
return false;
//TODO start the transaction
},
@@ -217,29 +214,78 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// what happens when a discount barcode is scanned : the default behavior
// is to set the discount on the last order.
barcode_discount_action: function(ean){
+ this.pos.proxy.scan_item_success(ean);
var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
if(last_orderline){
last_orderline.set_discount(ean.value)
}
},
+ // shows an action bar on the screen. The actionbar is automatically shown when you add a button
+ // with add_action_button()
+ show_action_bar: function(){
+ this.pos_widget.action_bar.show();
+ this.$element.css({'bottom':'105px'});
+ },
+
+ // hides the action bar. The actionbar is automatically hidden when it is empty
+ hide_action_bar: function(){
+ this.pos_widget.action_bar.hide();
+ this.$element.css({'bottom':'0px'});
+ },
+
+ // adds a new button to the action bar. The button definition takes three parameters, all optional :
+ // - label: the text below the button
+ // - icon: a small icon that will be shown
+ // - click: a callback that will be executed when the button is clicked.
+ // the method returns a reference to the button widget, and automatically show the actionbar.
+ add_action_button: function(button_def){
+ this.show_action_bar();
+ return this.pos_widget.action_bar.add_new_button(button_def);
+ },
+
// this method shows the screen and sets up all the widget related to this screen. Extend this method
// if you want to alter the behavior of the screen.
show: function(){
+ var self = this;
+
this.hidden = false;
if(this.$element){
this.$element.show();
}
+ if(this.pos_widget.action_bar.get_button_count() > 0){
+ this.show_action_bar();
+ }else{
+ this.hide_action_bar();
+ }
+
+ // we add the help button by default. we do this because the buttons are cleared on each refresh so that
+ // the button stay local to each screen
+ this.pos_widget.left_action_bar.add_new_button({
+ label: 'help',
+ icon: '/point_of_sale/static/src/img/icons/png48/help.png',
+ click: function(){ self.help_button_action(); },
+ });
+
var self = this;
var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode);
this.pos_widget.set_leftpane_visible(this.show_leftpane);
+ this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode);
this.pos_widget.set_cashier_controls_visible(cashier_mode);
- this.pos_widget.action_bar.set_element_visible('help-button', !cashier_mode, function(){ self.help_button_action(); });
- this.pos_widget.action_bar.set_element_visible('logout-button', cashier_mode && this.pos.use_selfcheckout, function(){ self.logout_button_action(); });
- this.pos_widget.action_bar.set_element_visible('close-button', cashier_mode, function(){ self.close_button_action(); });
+
+ if(cashier_mode && this.pos.iface_self_checkout){
+ this.pos_widget.client_button.show();
+ }else{
+ this.pos_widget.client_button.hide();
+ }
+ if(cashier_mode){
+ this.pos_widget.close_button.show();
+ }else{
+ this.pos_widget.close_button.hide();
+ }
this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
@@ -251,16 +297,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
});
},
-
// this method is called when the screen is closed to make place for a new screen. this is a good place
// to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
close: function(){
if(this.pos.barcode_reader){
this.pos.barcode_reader.reset_action_callbacks();
}
- if(this.pos_widget.action_bar){
- this.pos_widget.action_bar.destroy_buttons();
- }
+ this.pos_widget.action_bar.destroy_buttons();
+ this.pos_widget.left_action_bar.destroy_buttons();
},
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
@@ -349,6 +393,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
template:'ScaleInviteScreenWidget',
+ next_screen:'scale',
+ previous_screen:'products',
+
show: function(){
this._super();
var self = this;
@@ -359,21 +406,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
var weight = self.pos.proxy.weighting_read_kg();
if(weight > 0.001){
clearInterval(this.intervalID);
- self.pos_widget.screen_selector.set_current_screen('scale');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
}
},500);
- this.pos_widget.action_bar.add_new_button(
- {
+ this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
clearInterval(this.intervalID);
self.pos.proxy.weighting_end();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
- }
- );
+ });
},
close: function(){
this._super();
@@ -383,25 +428,30 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ScaleScreenWidget = module.ScreenWidget.extend({
template:'ScaleScreenWidget',
+
+ next_screen: 'products',
+ previous_screen: 'products',
+
show: function(){
this._super();
this.renderElement();
var self = this;
- this.pos_widget.action_bar.add_new_button({
+
+ this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
});
- this.validate_button = this.pos_widget.action_bar.add_new_button({
+ this.validate_button = this.add_action_button({
label: 'Validate',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
click: function(){
self.order_product();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
},
});
@@ -419,7 +469,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
this.$('.product-picture').click(function(){
self.order_product();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
});
},
get_product: function(){
@@ -444,7 +494,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
get_product_image: function(){
var product = this.get_product();
- return product ? product.get('image_medium') : undefined;
+ return product ? product.get('image') : undefined;
},
get_product_weight: function(){
return this.weight || 0;
@@ -458,6 +508,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
template:'ClientPaymentScreenWidget',
+
+ next_screen: 'welcome',
+ previous_screen: 'products',
+
show: function(){
this._super();
var self = this;
@@ -486,7 +540,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
self.pos.push_order(currentOrder.exportAsJSON()).then(function() {
currentOrder.destroy();
self.pos.proxy.transaction_end();
- self.pos_widget.screen_selector.set_current_screen('welcome');
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
});
}else if(payment === 'payment_rejected'){
clearInterval(this.intervalID);
@@ -494,17 +548,15 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
},500);
- this.pos_widget.action_bar.add_new_button(
- {
+ this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){ //TODO Go to ask for weighting screen
+ click: function(){
clearInterval(this.intervalID);
self.pos.proxy.payment_canceled();
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
- }
- );
+ });
},
close: function(){
this._super();
@@ -515,23 +567,32 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.WelcomeScreenWidget = module.ScreenWidget.extend({
template:'WelcomeScreenWidget',
+ next_screen: 'products',
+
show_numpad: false,
show_leftpane: false,
barcode_client_action: function(ean){
this._super(ean);
- this.pos_widget.screen_selector.set_current_screen('products');
+ this.pos_widget.screen_selector.set_current_screen(this.next_screen);
},
show: function(){
this._super();
var self = this;
+ $('.goodbye-message').css({opacity:1}).show();
+ setTimeout(function(){
+ $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();});
+ },3000);
},
});
module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget',
+ scale_screen: 'scale_invite',
+ client_next_screen: 'client_payment',
+
show_numpad: true,
show_leftpane: true,
@@ -542,8 +603,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_list_widget = new module.ProductListWidget(this,{
click_product_action: function(product){
- if(product.get('to_weight') && self.pos.use_scale){
- self.pos_widget.screen_selector.set_current_screen('scale_invite', {product: product});
+ if(product.get('to_weight') && self.pos.iface_electronic_scale){
+ self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product});
}else{
self.pos.get('selectedOrder').addProduct(product);
}
@@ -559,16 +620,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_categories_widget.reset_category();
this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state);
- if(this.pos.use_virtual_keyboard){
+ if(this.pos.iface_vkeyboard){
this.pos_widget.onscreen_keyboard.connect();
}
if(this.pos_widget.screen_selector.current_mode === 'client'){
- this.pos_widget.action_bar.add_new_button({
+ this.add_action_button({
label: 'pay',
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
click: function(){
- self.pos_widget.screen_selector.set_current_screen('client_payment');
+ self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
}
});
}
@@ -604,17 +665,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
- this.pos_widget.action_bar.add_new_button({
+ this.add_action_button({
label: 'Print',
icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
click: function(){ self.print(); },
});
- this.pos_widget.action_bar.add_new_button({
+ this.add_action_button({
label: 'Next Order',
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
click: function() { self.finishOrder(); },
});
+
+ window.print();
},
print: function() {
window.print();
@@ -643,6 +706,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.PaymentScreenWidget = module.ScreenWidget.extend({
template: 'PaymentScreenWidget',
+ back_screen: 'products',
+ next_screen: 'receipt',
init: function(parent, options) {
this._super(parent,options);
this.model = options.model;
@@ -654,21 +719,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
- if(this.pos.use_cashbox){
+ if(this.pos.iface_cashdrawer){
this.pos.proxy.open_cashbox();
}
this.set_numpad_state(this.pos_widget.numpad.state);
- this.back_button = this.pos_widget.action_bar.add_new_button({
+ this.back_button = this.add_action_button({
label: 'Back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
- self.pos_widget.screen_selector.set_current_screen('products');
+ self.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
});
- this.validate_button = this.pos_widget.action_bar.add_new_button({
+ this.validate_button = this.add_action_button({
label: 'Validate',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
click: function(){
@@ -682,24 +747,18 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.payment_screen.set_numpad_state(null);
},
back: function() {
- this.pos_widget.screen_selector.set_current_screen('products');
+ this.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
validateCurrentOrder: function() {
- var self = this;
var currentOrder = this.pos.get('selectedOrder');
- this.validate_button.$element.attr('disabled','disabled'); //FIXME is the css actually using this attr ?
-
this.pos.push_order(currentOrder.exportAsJSON())
- .then(function() {
- self.validate_button.$element.removeAttr('disabled');
- if(self.pos.use_proxy_printer){
- self.pos.proxy.print_receipt(currentOrder.export_for_printing());
- self.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
- }else{
- self.pos_widget.screen_selector.set_current_screen('receipt');
- }
- });
+ if(this.pos.iface_print_via_proxy){
+ this.pos.proxy.print_receipt(currentOrder.export_for_printing());
+ this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
+ }else{
+ this.pos_widget.screen_selector.set_current_screen(this.next_screen);
+ }
},
bindPaymentLineEvents: function() {
this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
diff --git a/addons/point_of_sale/static/src/js/pos_basewidget.js b/addons/point_of_sale/static/src/js/widget_base.js
similarity index 100%
rename from addons/point_of_sale/static/src/js/pos_basewidget.js
rename to addons/point_of_sale/static/src/js/widget_base.js
diff --git a/addons/point_of_sale/static/src/js/pos_keyboard_widget.js b/addons/point_of_sale/static/src/js/widget_keyboard.js
similarity index 100%
rename from addons/point_of_sale/static/src/js/pos_keyboard_widget.js
rename to addons/point_of_sale/static/src/js/widget_keyboard.js
diff --git a/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js b/addons/point_of_sale/static/src/js/widget_scrollbar.js
similarity index 99%
rename from addons/point_of_sale/static/src/js/pos_scrollbar_widget.js
rename to addons/point_of_sale/static/src/js/widget_scrollbar.js
index d4f6903c435..7c765028231 100644
--- a/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js
+++ b/addons/point_of_sale/static/src/js/widget_scrollbar.js
@@ -65,6 +65,7 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_
this.auto_hide(false);
this.$element.bind('mousewheel',function(event,delta){
self.scroll(delta*self.wheel_step);
+ return false;
});
this.$element.bind('click',function(event){
var vpos = event.pageY - self.$element.offset().top;
diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/widgets.js
similarity index 75%
rename from addons/point_of_sale/static/src/js/pos_widgets.js
rename to addons/point_of_sale/static/src/js/widgets.js
index 9a27be29381..c3bd73ca35a 100644
--- a/addons/point_of_sale/static/src/js/pos_widgets.js
+++ b/addons/point_of_sale/static/src/js/widgets.js
@@ -1,6 +1,57 @@
function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
+ // The ImageCache is used to hide the latency of the application cache on-disk access in chrome
+ // that causes annoying flickering on product pictures. Why the hell a simple access to
+ // the application cache involves such latency is beyond me, hopefully one day this can be
+ // removed.
+ module.ImageCache = instance.web.Class.extend({
+ init: function(options){
+ options = options || {};
+ this.max_size = options.max_size || 500;
+
+ this.cache = {};
+ this.access_time = {};
+ this.size = 0;
+ },
+ get_image_uncached: function(url){
+ var img = new Image();
+ img.src = url;
+ return img;
+ },
+ // returns a DOM Image object from an url, and cache the last 500 (by default) results
+ get_image: function(url){
+ var cached = this.cache[url];
+ if(cached){
+ this.access_time[url] = (new Date()).getTime();
+ return cached;
+ }else{
+ var img = new Image();
+ img.src = url;
+ while(this.size >= this.max_size){
+ var oldestUrl = null;
+ var oldestTime = (new Date()).getTime();
+ for(var url in this.cache){
+ var time = this.access_time[url];
+ if(time <= oldestTime){
+ oldestTime = time;
+ oldestUrl = url;
+ }
+ }
+ if(oldestUrl){
+ delete this.cache[oldestUrl];
+ delete this.access_time[oldestUrl];
+ }
+ this.size--;
+ }
+ this.cache[url] = img;
+ this.access_time[url] = (new Date()).getTime();
+ this.size++;
+ return img;
+ }
+ },
+ });
+
module.NumpadWidget = module.PosBaseWidget.extend({
template:'NumpadWidget',
init: function(parent, options) {
@@ -112,7 +163,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
template:'OrderWidget',
init: function(parent, options) {
this._super(parent,options);
- this.compact = false;
+ this.display_mode = options.display_mode || 'numpad'; // 'maximized' | 'actionbar' | 'numpad'
this.set_numpad_state(options.numpadState);
this.pos.bind('change:selectedOrder', this.change_selected_order, this);
this.bind_orderline_events();
@@ -165,8 +216,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var self = this;
this._super();
- if(!this.compact){
+ if(this.display_mode === 'maximized'){
$('.point-of-sale .order-container').css({'bottom':'0px'});
+ }else if(this.display_mode === 'actionbar'){
+ $('.point-of-sale .order-container').css({'bottom':'105px'});
+ }else if(this.display_mode !== 'numpad'){
+ console.error('ERROR: OrderWidget renderElement(): wrong display_mode:',this.display_mode);
}
var $content = this.$('.orderlines');
@@ -211,9 +266,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var total = order ? order.getTotal() : 0;
this.$('.summary .value.total').html(this.format_currency(total));
},
- set_compact: function(compact){
- if(this.compact !== compact){
- this.compact = compact;
+ set_display_mode: function(mode){
+ if(this.display_mode !== mode){
+ this.display_mode = mode;
this.renderElement();
}
},
@@ -225,20 +280,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this._super(parent,options);
this.model = options.model;
this.model.attributes.weight = options.weight;
- this.next_screen = options.next_screen;
- this.click_product_action = options.click_product_action;
+ this.next_screen = options.next_screen; //when a product is clicked, this screen is set
+ this.click_product_action = options.click_product_action;
},
- add_to_order: function(event) {
- /* Preserve the category URL */
- event.preventDefault();
- return (this.pos.get('selectedOrder')).addProduct(this.model);
- },
- set_weight: function(weight){
- this.model.attributes.weight = weight;
- this.renderElement();
+ // returns the url of the product thumbnail
+ get_image_url: function() {
+ return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.model.get('id');
},
renderElement: function() {
this._super();
+ this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.get_image_url()));
var self = this;
$("a", this.$element).click(function(e){
if(self.click_product_action){
@@ -313,6 +364,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
module.ActionButtonWidget = instance.web.Widget.extend({
template:'ActionButtonWidget',
+ icon_template:'ActionButtonWidgetWithIcon',
init: function(parent, options){
this._super(parent, options);
this.label = options.label || 'button';
@@ -320,7 +372,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.click_action = options.click;
if(options.icon){
this.icon = options.icon;
- this.template = 'ActionButtonWidgetWithIcon';
+ this.template = this.icon_template;
}
},
renderElement: function(){
@@ -359,21 +411,25 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.button_list = [];
return this;
},
+ get_button_count: function(){
+ return this.button_list.length;
+ },
add_new_button: function(button_options){
- if(arguments.length == 1){
- var button = new module.ActionButtonWidget(this,button_options);
- this.button_list.push(button);
- button.appendTo($('.pos-actionbar-button-list'));
- return button;
- }else{
- for(var i = 0; i < arguments.length; i++){
- this.add_new_button(arguments[i]);
- }
- }
- return undefined;
+ var button = new module.ActionButtonWidget(this,button_options);
+ this.button_list.push(button);
+ button.appendTo(this.$('.pos-actionbar-button-list'));
+ return button;
+ },
+ show:function(){
+ this.$element.show();
+ },
+ hide:function(){
+ this.$element.hide();
},
});
+ module.CategoryButton = module.PosBaseWidget.extend({
+ });
module.ProductCategoriesWidget = module.PosBaseWidget.extend({
template: 'ProductCategoriesWidget',
init: function(parent, options){
@@ -389,41 +445,53 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// changes the category. if undefined, sets to root category
set_category : function(category){
+ var db = this.pos.db;
if(!category){
- this.category = this.pos.root_category;
+ this.category = db.get_category_by_id(db.root_category_id);
}else{
this.category = category;
}
-
this.breadcrumb = [];
- for(var i = 1; i < this.category.ancestors.length; i++){
- this.breadcrumb.push(this.category.ancestors[i]);
+ var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
+ for(var i = 1; i < ancestors_ids.length; i++){
+ this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
}
- if(this.category !== this.pos.root_category){
+ if(this.category.id !== db.root_category_id){
this.breadcrumb.push(this.category);
}
- if(this.product_type === 'weightable'){
- this.subcategories = [];
- for(var i = 0; i < this.category.childrens.length; i++){
- if(this.category.childrens[i].weightable_product_list.length > 0){
- this.subcategories.push( this.category.childrens[i]);
- }
- }
- }else{
- this.subcategories = this.category.childrens || [];
- }
+ this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
+ },
+
+ get_image_url: function(category){
+ return '/web/binary/image?session_id='+instance.session.session_id+'&model=pos.category&field=image&id='+category.id;
},
renderElement: function(){
var self = this;
this._super();
- _.each(this.subcategories, function(category){
- var button = QWeb.render('CategoryButton',{category:category});
- button = _.str.trim(button);
- $(button).appendTo(this.$('.category-list')).click(function(event){
+ var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
+ _.each(this.subcategories, function(category){
+ if(category.image){
+ hasimages = true;
+ }
+ });
+
+ _.each(this.subcategories, function(category){
+ if(hasimages){
+ var button = QWeb.render('CategoryButton',{category:category});
+ var button = _.str.trim(button);
+ var button = $(button);
+ button.find('img').replaceWith(self.pos_widget.image_cache.get_image(self.get_image_url(category)));
+ }else{
+ var button = QWeb.render('CategorySimpleButton',{category:category});
+ button = _.str.trim(button); // we remove whitespace between buttons to fix spacing
+ var button = $(button);
+ }
+
+ button.appendTo(this.$('.category-list')).click(function(event){
var id = category.id;
- var cat = self.pos.categories_by_id[id];
+ var cat = self.pos.db.get_category_by_id(id);
self.set_category(cat);
self.renderElement();
self.search_and_categories(cat);
@@ -432,7 +500,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// breadcrumb click actions
this.$(".oe-pos-categories-list a").click(function(event){
var id = $(event.target).data("category-id");
- var category = self.pos.categories_by_id[id];
+ var category = self.pos.db.get_category_by_id(id);
self.set_category(category);
self.renderElement();
self.search_and_categories(category);
@@ -455,47 +523,30 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// filters the products, and sets up the search callbacks
search_and_categories: function(category){
var self = this;
-
- var all_products = this.pos.get('product_list');
- var all_packages = this.pos.get('product.packaging');
// find all products belonging to the current category
- var products = [];
- if(this.product_type === 'weightable'){
- products = all_products.filter( function(product){
- return self.category.weightable_product_set[product.id];
- });
- }else{
- products = all_products.filter( function(product){
- return self.category.product_set[product.id];
- });
- }
+ var products = this.pos.db.get_product_by_category(this.category.id);
+ self.pos.get('products').reset(products);
- // product lists watch for reset events on 'products' to re-render.
- // FIXME that means all productlist widget re-render... even the hidden ones !
- this.pos.get('products').reset(products);
-
- // find all the products whose name match the query in the searchbox
+ // filter the products according to the search string
this.$('.searchbox input').keyup(function(){
- var results, search_str;
- search_str = $(this).val().toLowerCase();
- if(search_str){
- results = products.filter( function(p){
- return p.name.toLowerCase().indexOf(search_str) != -1 ||
- (p.ean13 && p.ean13.indexOf(search_str) != -1);
- });
- self.$element.find('.search-clear').fadeIn();
+ query = $(this).val().toLowerCase();
+ if(query){
+ var products = self.pos.db.search_product_in_category(self.category.id, query);
+ self.pos.get('products').reset(products);
+ self.$('.search-clear').fadeIn();
}else{
- results = products;
- self.$element.find('.search-clear').fadeOut();
+ var products = self.pos.db.get_product_by_category(self.category.id);
+ self.pos.get('products').reset(products);
+ self.$('.search-clear').fadeOut();
}
- self.pos.get('products').reset(results);
- });
- this.$('.searchbox input').click(function(){
});
+ this.$('.searchbox input').click(function(){}); //Why ???
+
//reset the search when clicking on reset
this.$('.search-clear').click(function(){
+ var products = self.pos.db.get_product_by_category(self.category.id);
self.pos.get('products').reset(products);
self.$('.searchbox input').val('').focus();
self.$('.search-clear').fadeOut();
@@ -519,11 +570,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.renderElement();
});
},
- set_weight: function(weight){
- for(var i = 0; i < this.product_list.length; i++){
- this.product_list[i].set_weight(weight);
- }
- },
renderElement: function() {
var self = this;
this._super();
@@ -586,14 +632,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
});
+ module.HeaderButtonWidget = module.PosBaseWidget.extend({
+ template: 'HeaderButtonWidget',
+ init: function(parent, options){
+ options = options || {};
+ this._super(parent, options);
+ this.action = options.action;
+ this.label = options.label;
+ },
+ renderElement: function(){
+ var self = this;
+ this._super();
+ if(this.action){
+ this.$element.click(function(){ self.action(); });
+ }
+ },
+ show: function(){ this.$element.show(); },
+ hide: function(){ this.$element.hide(); },
+
+ });
+
+
// ---------- Main Point of Sale Widget ----------
// this is used to notify the user that data is being synchronized on the network
module.SynchNotificationWidget = module.PosBaseWidget.extend({
template: "SynchNotificationWidget",
- init: function(parent,options) {
+ init: function(parent, options){
options = options || {};
- this._super(parent,options);
+ this._super(parent, options);
},
renderElement: function() {
var self = this;
@@ -632,9 +699,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
this.numpad_visible = true;
+ this.left_action_bar_visible = true;
this.leftpane_visible = true;
this.leftpane_width = '440px';
this.cashier_controls_visible = true;
+ this.image_cache = new module.ImageCache(); // for faster products image display
+
+ /*
+ //Epileptic mode
+ setInterval(function(){
+ $('body').css({'-webkit-filter':'hue-rotate('+Math.random()*360+'deg)' });
+ },100);
+ */
+
},
start: function() {
@@ -643,7 +720,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.build_currency_template();
self.renderElement();
- self.$('.neworder-button').click(_.bind(self.create_new_order, self));
+ self.$('.neworder-button').click(function(){
+ self.pos.add_new_order();
+ });
//when a new order is created, add an order button widget
self.pos.get('orders').bind('add', function(new_order){
@@ -659,6 +738,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.build_widgets();
+ self.screen_selector.set_default_screen();
+
+ self.pos.barcode_reader.connect();
+
instance.webclient.set_content_full_screen(true);
if (!self.pos.get('pos_session')) {
@@ -667,9 +750,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
}
- self.$('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();});
+ self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
self.$('.loader img').hide();
+ if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
+ window.pos = self.pos;
+ window.pos_widget = self.pos_widget;
+ }
+
},function(){ // error when loading models data from the backend
self.$('.loader img').hide();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
@@ -682,8 +770,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}, self));
});
},
-
+
+ // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
+ // startup screen, etc, override this method.
build_widgets: function() {
+ var self = this;
// -------- Screens ---------
@@ -725,13 +816,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// -------- Misc ---------
this.notification = new module.SynchNotificationWidget(this,{});
- this.notification.replace(this.$('.placeholder-SynchNotificationWidget'));
+ this.notification.appendTo(this.$('#rightheader'));
this.username = new module.UsernameWidget(this,{});
this.username.replace(this.$('.placeholder-UsernameWidget'));
this.action_bar = new module.ActionBarWidget(this);
- this.action_bar.appendTo($(".point-of-sale #content"));
+ this.action_bar.appendTo($(".point-of-sale #rightpane"));
+
+ this.left_action_bar = new module.ActionBarWidget(this);
+ this.left_action_bar.appendTo($(".point-of-sale #leftpane"));
this.paypad = new module.PaypadWidget(this, {});
this.paypad.replace($('#placeholder-PaypadWidget'));
@@ -746,6 +840,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
'keyboard_model': 'simple'
});
this.onscreen_keyboard.appendTo($(".point-of-sale #content"));
+
+ this.close_button = new module.HeaderButtonWidget(this,{
+ label:'Close',
+ action: function(){ self.try_close(); },
+ });
+ this.close_button.appendTo(this.$('#rightheader'));
+
+ this.client_button = new module.HeaderButtonWidget(this,{
+ label:'Self-Checkout',
+ action: function(){ self.screen_selector.set_user_mode('client'); },
+ });
+ this.client_button.appendTo(this.$('#rightheader'));
+
// -------- Screen Selector ---------
@@ -768,59 +875,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
default_client_screen: 'welcome',
default_cashier_screen: 'products',
- default_mode: this.pos.use_selfcheckout ? 'client' : 'cashier',
+ default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier',
});
- this.screen_selector.set_default_screen();
-
- this.pos.barcode_reader.connect();
},
- //FIXME this method is probably not at the right place ...
- scan_product: function(parsed_ean){
- var selectedOrder = this.pos.get('selectedOrder');
- var scannedProductModel = this.get_product_by_ean(parsed_ean);
- if (!scannedProductModel){
- return false;
- } else {
- if(parsed_ean.type === 'price'){
- selectedOrder.addProduct(new module.Product(scannedProductModel), { price:parsed_ean.value});
- }else if(parsed_ean.type === 'weight'){
- selectedOrder.addProduct(new module.Product(scannedProductModel), { quantity:parsed_ean.value, merge:false});
- }else{
- selectedOrder.addProduct(new module.Product(scannedProductModel));
- }
- return true;
- }
- },
-
- get_product_by_ean: function(parsed_ean) {
- var allProducts = this.pos.get('product_list');
- var allPackages = this.pos.get('product.packaging');
- var scannedProductModel = undefined;
-
- if (parsed_ean.type === 'price' || parsed_ean.type === 'weight') {
- var itemCode = parsed_ean.id;
- var scannedPackaging = _.detect(allPackages, function(pack) {
- return pack.ean && pack.ean.substring(0,7) === itemCode;
- });
- if (scannedPackaging) {
- scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
- }else{
- scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);});
- }
- } else if(parsed_ean.type === 'unit'){
- scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === parsed_ean.ean;}); //TODO DOES NOT SCALE
- }
- return scannedProductModel;
- },
- // creates a new order, and add it to the list of orders.
- create_new_order: function() {
- var new_order;
- new_order = new module.Order({ pos: this.pos });
- this.pos.get('orders').add(new_order);
- this.pos.set({ selectedOrder: new_order });
- },
changed_pending_operations: function () {
var self = this;
this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
@@ -830,16 +889,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible !== this.numpad_visible){
this.numpad_visible = visible;
if(visible){
+ this.set_left_action_bar_visible(false);
this.numpad.show();
this.paypad.show();
- this.order_widget.set_compact(true);
+ this.order_widget.set_display_mode('numpad');
}else{
this.numpad.hide();
this.paypad.hide();
- this.order_widget.set_compact(false);
+ if(this.order_widget.display_mode === 'numpad'){
+ this.order_widget.set_display_mode('maximized');
+ }
}
}
},
+ set_left_action_bar_visible: function(visible){
+ if(visible !== this.left_action_bar_visible){
+ this.left_action_bar_visible = visible;
+ if(visible){
+ this.set_numpad_visible(false);
+ this.left_action_bar.show();
+ this.order_widget.set_display_mode('actionbar');
+ }else{
+ this.left_action_bar.hide();
+ if(this.order_widget.display_mode === 'actionbar'){
+ this.order_widget.set_display_mode('maximized');
+ }
+ }
+ }
+ },
+
//shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
set_leftpane_visible: function(visible){
if(visible !== this.leftpane_visible){
@@ -874,12 +952,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
});
},
close: function() {
+ var self = this;
this.pos.barcode_reader.disconnect();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
_.bind(function(res) {
return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
var action = result.result;
action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
+ //self.destroy();
this.do_action(action);
}, this));
}, this));
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 9bdac93d990..81f929d50e2 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -15,7 +15,7 @@
-
+
@@ -44,6 +44,12 @@
+
+
+
+
+
+
There are pending operations that could not be saved into the database, are you sure you want to exit?