Merge pull request #3332 from akretion/8.0-prepare-analytic-pos
authorFrédéric Van der Essen <fva@openerp.com>
Tue, 4 Nov 2014 10:39:03 +0000 (11:39 +0100)
committerFrédéric Van der Essen <fva@openerp.com>
Tue, 4 Nov 2014 10:39:03 +0000 (11:39 +0100)
point_of_sale: Add _prepare_analytic_account method

117 files changed:
addons/account/account_invoice.py
addons/account/account_move_line.py
addons/account/partner.py
addons/account/report/account_partner_balance.py
addons/account/security/account_security.xml
addons/account_analytic_plans/account_analytic_plans.py
addons/base_iban/base_iban.py
addons/board/static/src/css/dashboard.css
addons/board/static/src/css/dashboard.sass
addons/bus/static/src/js/bus.js
addons/hr_timesheet_invoice/report/account_analytic_profit.py
addons/l10n_fr/__openerp__.py
addons/l10n_fr/views/report_l10nfrbilan.xml
addons/pad/static/src/js/pad.js
addons/payment_paypal/controllers/main.py
addons/point_of_sale/report/pos_details.py
addons/point_of_sale/views/report_detailsofsales.xml
addons/product/pricelist.py
addons/product/product.py
addons/product/tests/__init__.py
addons/product/tests/test_pricelist.py [new file with mode: 0644]
addons/product_visible_discount/product_visible_discount.py
addons/purchase/purchase_view.xml
addons/purchase/views/report_purchaseorder.xml
addons/purchase/views/report_purchasequotation.xml
addons/report/static/src/js/qwebactionmanager.js
addons/sale/wizard/sale_line_invoice.py
addons/sale_margin/sale_margin.py
addons/web/static/src/xml/base.xml
addons/web_calendar/static/src/js/web_calendar.js
addons/web_diagram/controllers/main.py
addons/web_graph/doc/index.rst
addons/website/views/website_templates.xml
addons/website_blog/models/website_blog.py
addons/website_blog/views/website_blog_templates.xml
addons/website_sale/controllers/main.py
addons/website_sale/static/src/js/website_sale.js
addons/website_sale/views/templates.xml
debian/postinst
doc/_themes/odoodoc/github.py
doc/_themes/odoodoc/layout.html
doc/conf.py
doc/guides.rst [deleted file]
doc/guides/deployment.rst [deleted file]
doc/guides/forms.rst [deleted file]
doc/guides/forms/header.png [deleted file]
doc/guides/forms/header2.png [deleted file]
doc/guides/forms/header3.png [deleted file]
doc/guides/forms/nosheet.png [deleted file]
doc/guides/forms/oppreadonly.png [deleted file]
doc/guides/forms/placeholder.png [deleted file]
doc/guides/forms/screenshot-00.png [deleted file]
doc/guides/forms/screenshot-01.png [deleted file]
doc/guides/forms/screenshot-02.png [deleted file]
doc/guides/forms/screenshot-03.png [deleted file]
doc/guides/forms/screenshot-04.png [deleted file]
doc/guides/forms/sheet.png [deleted file]
doc/guides/forms/status.png [deleted file]
doc/guides/forms/status1.png [deleted file]
doc/guides/forms/status2.png [deleted file]
doc/guides/forms/wizard-popup.png [deleted file]
doc/guides/snippets.rst [deleted file]
doc/guides/themes.rst [deleted file]
doc/guides/workflows.rst [deleted file]
doc/howtos/themes.rst [new file with mode: 0644]
doc/howtos/website.rst
doc/images/inheritance_methods.png
doc/images/view-on-github.png
doc/index.rst
doc/reference.rst
doc/reference/forms/header.png [new file with mode: 0644]
doc/reference/forms/header2.png [new file with mode: 0644]
doc/reference/forms/header3.png [new file with mode: 0644]
doc/reference/forms/nosheet.png [new file with mode: 0644]
doc/reference/forms/oppreadonly.png [new file with mode: 0644]
doc/reference/forms/placeholder.png [new file with mode: 0644]
doc/reference/forms/screenshot-00.png [new file with mode: 0644]
doc/reference/forms/screenshot-01.png [new file with mode: 0644]
doc/reference/forms/screenshot-02.png [new file with mode: 0644]
doc/reference/forms/screenshot-03.png [new file with mode: 0644]
doc/reference/forms/screenshot-04.png [new file with mode: 0644]
doc/reference/forms/sheet.png [new file with mode: 0644]
doc/reference/forms/status.png [new file with mode: 0644]
doc/reference/forms/status1.png [new file with mode: 0644]
doc/reference/forms/status2.png [new file with mode: 0644]
doc/reference/forms/wizard-popup.png [new file with mode: 0644]
doc/reference/images/runner.png
doc/reference/images/runner2.png
doc/reference/images/tests.png
doc/reference/images/tests2.png
doc/reference/images/tests3.png
doc/reference/reports.rst
doc/reference/views.rst
doc/reference/workflow/Makefile [new file with mode: 0644]
doc/reference/workflow/join.dot [new file with mode: 0644]
doc/reference/workflow/join.png [new file with mode: 0644]
doc/reference/workflow/join.svg [new file with mode: 0644]
doc/reference/workflow/order_0.dot [new file with mode: 0644]
doc/reference/workflow/order_0.png [new file with mode: 0644]
doc/reference/workflow/order_0.svg [new file with mode: 0644]
doc/reference/workflow/order_1.dot [new file with mode: 0644]
doc/reference/workflow/order_1.png [new file with mode: 0644]
doc/reference/workflow/order_1.svg [new file with mode: 0644]
doc/reference/workflow/split.dot [new file with mode: 0644]
doc/reference/workflow/split.png [new file with mode: 0644]
doc/reference/workflow/split.svg [new file with mode: 0644]
doc/reference/workflows.rst [new file with mode: 0644]
doc/tutorials.rst
openerp/fields.py
openerp/http.py
openerp/models.py
openerp/modules/registry.py
openerp/osv/expression.py
openerp/osv/fields.py
openerp/service/server.py
openerp/sql_db.py
openerp/tools/func.py

index af7e506..f7d372e 100644 (file)
@@ -318,16 +318,26 @@ class account_invoice(models.Model):
     @api.model
     def fields_view_get(self, view_id=None, view_type=False, toolbar=False, submenu=False):
         context = self._context
+
+        def get_view_id(xid, name):
+            try:
+                return self.env['ir.model.data'].xmlid_to_res_id('account.' + xid, raise_if_not_found=True)
+            except ValueError:
+                try:
+                    return self.env['ir.ui.view'].search([('name', '=', name)], limit=1).id
+                except Exception:
+                    return False    # view not found
+
         if context.get('active_model') == 'res.partner' and context.get('active_ids'):
             partner = self.env['res.partner'].browse(context['active_ids'])[0]
             if not view_type:
-                view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.tree')]).id
+                view_id = get_view_id('invoice_tree', 'account.invoice.tree')
                 view_type = 'tree'
             elif view_type == 'form':
                 if partner.supplier and not partner.customer:
-                    view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.supplier.form')]).id
+                    view_id = get_view_id('invoice_supplier_form', 'account.invoice.supplier.form')
                 elif partner.customer and not partner.supplier:
-                    view_id = self.env['ir.ui.view'].search([('name', '=', 'account.invoice.form')]).id
+                    view_id = get_view_id('invoice_form', 'account.invoice.form')
 
         res = super(account_invoice, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
 
index 3fb7287..6daa844 100644 (file)
@@ -274,8 +274,13 @@ class account_move_line(osv.osv):
             journal_data = journal_obj.browse(cr, uid, context['journal_id'], context=context)
             account = total > 0 and journal_data.default_credit_account_id or journal_data.default_debit_account_id
             #map the account using the fiscal position of the partner, if needed
-            part = data.get('partner_id') and partner_obj.browse(cr, uid, data['partner_id'], context=context) or False
-            if account and data.get('partner_id'):
+            if isinstance(data.get('partner_id'), (int, long)):
+                part = partner_obj.browse(cr, uid, data['partner_id'], context=context)
+            elif isinstance(data.get('partner_id'), (tuple, list)):
+                part = partner_obj.browse(cr, uid, data['partner_id'][0], context=context)
+            else:
+                part = False
+            if account and part:
                 account = fiscal_pos_obj.map_account(cr, uid, part and part.property_account_position or False, account.id)
                 account = account_obj.browse(cr, uid, account, context=context)
             data['account_id'] =  account and account.id or False
@@ -434,7 +439,7 @@ class account_move_line(osv.osv):
                 move[line.move_id.id] = True
         move_line_ids = []
         if move:
-            move_line_ids = self.pool.get('account.move.line').search(cr, uid, [('journal_id','in',move.keys())], context=context)
+            move_line_ids = self.pool.get('account.move.line').search(cr, uid, [('move_id','in',move.keys())], context=context)
         return move_line_ids
 
 
index fa6dd0e..c0a1a69 100644 (file)
@@ -79,11 +79,13 @@ class account_fiscal_position(osv.osv):
     def map_tax(self, taxes):
         result = self.env['account.tax'].browse()
         for tax in taxes:
+            tax_count = 0
             for t in self.tax_ids:
                 if t.tax_src_id == tax:
+                    tax_count += 1
                     if t.tax_dest_id:
                         result |= t.tax_dest_id
-            else:
+            if not tax_count:
                 result |= tax
         return result
 
index ecd2875..51fff6c 100644 (file)
@@ -33,10 +33,6 @@ class partner_balance(report_sxw.rml_parse, common_report_header):
         self.account_ids = []
         self.localcontext.update( {
             'time': time,
-            'lines': self.lines,
-            'sum_debit': self._sum_debit,
-            'sum_credit': self._sum_credit,
-            'sum_litige': self._sum_litige,
             'get_fiscalyear': self._get_fiscalyear,
             'get_journal': self._get_journal,
             'get_filter': self._get_filter,
@@ -70,7 +66,20 @@ class partner_balance(report_sxw.rml_parse, common_report_header):
                     "WHERE a.type IN %s " \
                     "AND a.active", (self.ACCOUNT_TYPE,))
         self.account_ids = [a for (a,) in self.cr.fetchall()]
-        return super(partner_balance, self).set_context(objects, data, ids, report_type=report_type)
+        res = super(partner_balance, self).set_context(objects, data, ids, report_type=report_type)
+        lines = self.lines()
+        sum_debit = sum_credit = sum_litige = 0
+        for line in filter(lambda x: x['type'] == 3, lines):
+            sum_debit += line['debit'] or 0
+            sum_credit += line['credit'] or 0
+            sum_litige += line['enlitige'] or 0
+        self.localcontext.update({
+            'lines': lambda: lines,
+            'sum_debit': lambda: sum_debit,
+            'sum_credit': lambda: sum_credit,
+            'sum_litige': lambda: sum_litige,
+        })
+        return res
 
     def lines(self):
         move_state = ['draft','posted']
@@ -236,62 +245,6 @@ class partner_balance(report_sxw.rml_parse, common_report_header):
             i = i + 1
         return completearray
 
-    def _sum_debit(self):
-        move_state = ['draft','posted']
-        if self.target_move == 'posted':
-            move_state = ['posted']
-
-        if not self.ids:
-            return 0.0
-        self.cr.execute(
-                "SELECT sum(debit) " \
-                "FROM account_move_line AS l " \
-                "JOIN account_move am ON (am.id = l.move_id)" \
-                "WHERE l.account_id IN %s"  \
-                    "AND am.state IN %s" \
-                    "AND " + self.query + "",
-                    (tuple(self.account_ids), tuple(move_state)))
-        temp_res = float(self.cr.fetchone()[0] or 0.0)
-        return temp_res
-
-    def _sum_credit(self):
-        move_state = ['draft','posted']
-        if self.target_move == 'posted':
-            move_state = ['posted']
-
-        if not self.ids:
-            return 0.0
-        self.cr.execute(
-                "SELECT sum(credit) " \
-                "FROM account_move_line AS l " \
-                "JOIN account_move am ON (am.id = l.move_id)" \
-                "WHERE l.account_id IN %s" \
-                    "AND am.state IN %s" \
-                    "AND " + self.query + "",
-                    (tuple(self.account_ids), tuple(move_state)))
-        temp_res = float(self.cr.fetchone()[0] or 0.0)
-        return temp_res
-
-    def _sum_litige(self):
-        #gives the total of move lines with blocked boolean set to TRUE for the report selection
-        move_state = ['draft','posted']
-        if self.target_move == 'posted':
-            move_state = ['posted']
-
-        if not self.ids:
-            return 0.0
-        self.cr.execute(
-                "SELECT sum(debit-credit) " \
-                "FROM account_move_line AS l " \
-                "JOIN account_move am ON (am.id = l.move_id)" \
-                "WHERE l.account_id IN %s" \
-                    "AND am.state IN %s" \
-                    "AND " + self.query + " " \
-                    "AND l.blocked=TRUE ",
-                    (tuple(self.account_ids), tuple(move_state), ))
-        temp_res = float(self.cr.fetchone()[0] or 0.0)
-        return temp_res
-
     def _get_partners(self):
 
         if self.result_selection == 'customer':
index 65d3e95..3b3ab80 100644 (file)
         <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
     </record>
 
+    <record id="analytic_entry_analysis_comp_rule" model="ir.rule">
+        <field name="name">Analytic Entries Analysis multi-company</field>
+        <field name="model_id" ref="model_analytic_entries_report"/>
+        <field name="global" eval="True"/>
+        <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
+    </record>
+
     <record id="account_fiscal_position_comp_rule" model="ir.rule">
         <field name="name">Account fiscal Mapping company rule</field>
         <field name="model_id" ref="model_account_fiscal_position"/>
index 02fc371..4bd3822 100644 (file)
@@ -392,7 +392,7 @@ class account_invoice(osv.osv):
                 if inv.type in ('in_invoice', 'in_refund'):
                     ref = inv.reference
                 else:
-                    ref = self._convert_ref(inv.number)
+                    ref = inv.number
                 obj_move_line = acct_ins_obj.browse(cr, uid, il['analytics_id'], context=context)
                 ctx = context.copy()
                 ctx.update({'date': inv.date_invoice})
index d50ca33..8f2c63e 100644 (file)
@@ -177,7 +177,7 @@ class res_partner_bank(osv.osv):
         'iban': fields.related('acc_number', string='IBAN', size=34, readonly=True, help="International Bank Account Number", type="char"),
     }
     _constraints = [
-        (check_iban, _construct_constraint_msg, ["iban"]),
+        (check_iban, _construct_constraint_msg, ["iban", "acc_number", "state"]),
         (_check_bank, '\nPlease define BIC/Swift code on bank for bank type IBAN Account to make valid payments', ['bic'])
     ]
 
index f2bbcd0..2d5e2b0 100644 (file)
@@ -1,3 +1,4 @@
+@charset "UTF-8";
 .openerp .oe_dashboard_layout_1 .oe_dashboard_column.index_0 {
   width: 100%;
 }
index 122b6dc..33ff7ce 100644 (file)
@@ -1,3 +1,5 @@
+@charset "utf-8"
+
 @mixin radius($radius: 5px)
   -moz-border-radius: $radius
   -webkit-border-radius: $radius
index 6ca515f..393f101 100644 (file)
@@ -14,7 +14,7 @@
         },
         start_polling: function(){
             if(!this.activated){
-                setTimeout(this.poll(), 1);
+                this.poll();
                 this.stop = false;
             }
         },
@@ -58,4 +58,4 @@
     // singleton
     bus.bus = new bus.Bus();
     return bus;
-})();
\ No newline at end of file
+})();
index 3f7108f..15116ee 100644 (file)
@@ -39,13 +39,15 @@ class account_analytic_profit(report_sxw.rml_parse):
         return user_obj.browse(self.cr, self.uid, ids)
 
     def _journal_ids(self, form, user_id):
+        if isinstance(user_id, (int, long)):
+            user_id = [user_id]
         line_obj = self.pool['account.analytic.line']
         journal_obj = self.pool['account.analytic.journal']
         line_ids=line_obj.search(self.cr, self.uid, [
             ('date', '>=', form['date_from']),
             ('date', '<=', form['date_to']),
             ('journal_id', 'in', form['journal_ids'][0][2]),
-            ('user_id', '=', user_id),
+            ('user_id', 'in', user_id),
             ])
         ids=list(set([b.journal_id.id for b in line_obj.browse(self.cr, self.uid, line_ids)]))
         return journal_obj.browse(self.cr, self.uid, ids)
index 5bc3a82..2d232c5 100644 (file)
@@ -53,7 +53,7 @@ configuration of their taxes and fiscal positions manually.
 
 **Credits:** Sistheo, Zeekom, CrysaLEAD, Akretion and Camptocamp.
 """,
-    'depends': ['base_iban', 'account', 'account_chart', 'base_vat', 'l10n_fr_rib'],
+    'depends': ['base_iban', 'account', 'account_chart', 'base_vat'],
     'data': [
         'views/report_l10nfrbilan.xml',
         'views/report_l10nfrresultat.xml',
index 5b7335e..f5c697c 100644 (file)
@@ -20,6 +20,7 @@
                         </p>
                     </div>
                 </div>
+                <h3>Actif</h3>
                 <table class="table table-condensed">
                     <thead>
                         <tr>
                         </td>
                     </tr>
                 </table>
+
+                <h3>Passif</h3>
+                <table class="table table-condensed">
+                    <tbody>
+                        <tr>
+                            <td><strong>CAPITAUX PROPRES</strong></td>
+                            <td></td>
+                        </tr>
+                        <tr>
+                            <td>Capital [dont vers&#xE9;...]</td>
+                            <td><span t-esc="bpvar1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Primes d'&#xE9;mission, de fusion, d'apport</td>
+                            <td><span t-esc="bpvar2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>&#xC9;carts de r&#xE9;&#xE9;valuation</td>
+                            <td><span t-esc="bpvar3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>&#xC9;cart d'&#xE9;quivalence</td>
+                            <td><span t-esc="bpvar4" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td><strong>R&#xC9;SERVES</strong></td>
+                            <td></td>
+                        </tr>
+                        <tr>
+                            <td>R&#xE9;serve l&#xE9;gale</td>
+                            <td><span t-esc="bpvar5" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>R&#xE9;serves statutaires ou contractuelles</td>
+                            <td><span t-esc="bpvar6" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>R&#xE9;serves r&#xE9;glement&#xE9;es</td><td><span t-esc="bpvar7" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td></tr>
+                        <tr>
+                            <td>Autres r&#xE9;serves</td>
+                            <td><span t-esc="bpvar8" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Report &#xE0; nouveau</td>
+                            <td><span t-esc="bpvar9" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td><strong>R&#xC9;SULTAT DE L'EXERCICE [b&#xE9;n&#xE9;fice ou perte]</strong></td>
+                            <td><span t-esc="bpvar10" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Subventions d'investissement</td>
+                            <td><span t-esc="bpvar11" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Provisions r&#xE9;glement&#xE9;es</td>
+                            <td><span t-esc="bpvar12" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td class="text-right"><strong>TOTAL I</strong></td>
+                            <td><span t-esc="pt1" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td><strong>PROVISIONS</strong></td>
+                            <td></td>
+                        </tr>
+                        <tr>
+                            <td>Provisions pour risques</td>
+                            <td><span t-esc="bpvar13" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Provisions pour charges</td>
+                            <td><span t-esc="bpvar14" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td class="text-right"><strong>TOTAL II</strong></td>
+                            <td><span t-esc="pt2" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td><strong>DETTES</strong></td>
+                            <td></td>
+                        </tr>
+                        <tr>
+                            <td>Emprunts obligataires convertibles</td>
+                            <td><span t-esc="bpvar15" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Autres emprunts obligataires</td>
+                            <td><span t-esc="bpvar16" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Emprunts et dettes aupr&#xE8;s des &#xE9;tablissements de cr&#xE9;dit</td>
+                            <td><span t-esc="bpvar17" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Emprunts et dettes financi&#xE8;res diverses</td>
+                            <td><span t-esc="bpvar18" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Avances et acomptes re&#xE7;us sur commandes en cours</td>
+                            <td><span t-esc="bpvar19" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Dettes fournisseurs et comptes rattach&#xE9;s </td>
+                            <td><span t-esc="bpvar20" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Dettes fiscales et sociales</td>
+                            <td><span t-esc="bpvar21" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Dettes sur immobilisations et comptes rattach&#xE9;s</td>
+                            <td><span t-esc="bpvar22" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Autres dettes</td>
+                            <td><span t-esc="bpvar23" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Instruments de tr&#xE9;sorerie</td>
+                            <td><span t-esc="bpvar24" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>Produits constat&#xE9;s d'avance</td>
+                            <td><span t-esc="bpvar25" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td class="text-right"><strong>TOTAL III</strong></td>
+                            <td><span t-esc="pt3" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>&#xC9;carts de conversion passif <font face="Times-Roman">( IV )</font></td>
+                            <td><span t-esc="bpvar26" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td><strong>TOTAL G&#xC9;N&#xC9;RAL (I + II + III + IV)</strong></td>
+                            <td><span t-esc="passif" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                        <tr>
+                            <td>&amp;nbsp;</td>
+                            <td>&amp;nbsp;</td>
+                        </tr>
+                        <tr>
+                            <td><strong>ACTIF - PASSIF</strong></td>
+                            <td><span t-esc="round(actif-passif,2)" t-esc-options='{"widget": "monetary", "display_currency": "res_company.currency_id"}'/></td>
+                        </tr>
+                    </tbody>
+                </table>
             </div>
         </t>
     </t>
index cf6768e..a6f2ce6 100644 (file)
@@ -21,6 +21,7 @@ openerp.pad = function(instance) {
                 self.$el.toggleClass('oe_pad_fullscreen');
                 self.$el.find('.oe_pad_switch').toggleClass('fa-expand fa-compress');
                 self.view.$el.find('.oe_chatter').toggle();
+                $('#oe_main_menu_navbar').toggle();
             });
             this._configured_deferred.always(function() {
                 var configured = self.get('configured');
index 94c8171..67f89a8 100644 (file)
@@ -47,7 +47,7 @@ class PaypalController(http.Controller):
             tx_ids = request.registry['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context)
             if tx_ids:
                 tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
-        paypal_urls = request.registry['payment.acquirer']._get_paypal_urls(cr, uid, tx and tx.acquirer_id and tx.acquirer_id.env or 'prod', context=context)
+        paypal_urls = request.registry['payment.acquirer']._get_paypal_urls(cr, uid, tx and tx.acquirer_id and tx.acquirer_id.environment or 'prod', context=context)
         validate_url = paypal_urls['paypal_form_url']
         urequest = urllib2.Request(validate_url, werkzeug.url_encode(new_post))
         uopen = urllib2.urlopen(urequest)
index 1c1f46a..1d40746 100644 (file)
@@ -160,7 +160,7 @@ class pos_details(report_sxw.rml_parse):
         pos_ids = pos_order_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('state','in',['paid','invoiced','done']),('user_id','in',user_ids)])
         for order in pos_order_obj.browse(self.cr, self.uid, pos_ids):
             for line in order.lines:
-                line_taxes = account_tax_obj.compute_all(self.cr, self.uid, line.product_id.taxes_id, line.price_unit, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
+                line_taxes = account_tax_obj.compute_all(self.cr, self.uid, line.product_id.taxes_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
                 for tax in line_taxes['taxes']:
                     taxes.setdefault(tax['id'], {'name': tax['name'], 'amount':0.0})
                     taxes[tax['id']]['amount'] += tax['amount']
index 5f9fa48..648d409 100644 (file)
@@ -79,7 +79,7 @@
                     <tr t-if="gettaxamount(data['form'])"><td colspan="2"><strong>Taxes</strong></td></tr>
                     <tr t-foreach="gettaxamount(data['form'])" t-as="tax">
                         <td><span t-esc="tax['name']"/></td>
-                        <td class="text_right">
+                        <td class="text-right">
                             <strong t-esc="formatLang(tax['amount'], currency_obj = res_company.currency_id)"/>
                         </td>
                     </tr>
index 07efc90..d212a74 100644 (file)
@@ -251,7 +251,11 @@ class product_pricelist(osv.osv):
             price = False
             rule_id = False
             for rule in items:
-                if rule.min_quantity and qty<rule.min_quantity:
+                if 'uom' in context:
+                    qty_in_product_uom = product_uom_obj._compute_qty(cr, uid, context['uom'], qty, product.uom_id.id or product.uos_id.id)
+                else:
+                    qty_in_product_uom = qty
+                if rule.min_quantity and qty_in_product_uom<rule.min_quantity:
                     continue
                 if is_product_template:
                     if rule.product_tmpl_id and product.id != rule.product_tmpl_id.id:
@@ -315,7 +319,12 @@ class product_pricelist(osv.osv):
                     price = price * (1.0+(rule.price_discount or 0.0))
                     if rule.price_round:
                         price = tools.float_round(price, precision_rounding=rule.price_round)
-                    price += (rule.price_surcharge or 0.0)
+                    if context.get('uom'):
+                        # compute price_surcharge based on reference uom
+                        factor = product_uom_obj.browse(cr, uid, context.get('uom'), context=context).factor
+                    else:
+                        factor = 1.0
+                    price += (rule.price_surcharge or 0.0) / factor
                     if rule.price_min_margin:
                         price = max(price, price_limit+rule.price_min_margin)
                     if rule.price_max_margin:
index 3f54cba..0e38e80 100644 (file)
@@ -1118,6 +1118,34 @@ class product_product(osv.osv):
     def need_procurement(self, cr, uid, ids, context=None):
         return False
 
+    def _compute_uos_qty(self, cr, uid, ids, uom, qty, uos, context=None):
+        '''
+        Computes product's invoicing quantity in UoS from quantity in UoM.
+        Takes into account the
+        :param uom: Source unit
+        :param qty: Source quantity
+        :param uos: Target UoS unit.
+        '''
+        if not uom or not qty or not uos:
+            return qty
+        uom_obj = self.pool['product.uom']
+        product_id = ids[0] if isinstance(ids, (list, tuple)) else ids
+        product = self.browse(cr, uid, product_id, context=context)
+        if isinstance(uos, (int, long)):
+            uos = uom_obj.browse(cr, uid, uos, context=context)
+        if isinstance(uom, (int, long)):
+            uom = uom_obj.browse(cr, uid, uom, context=context)
+        if product.uos_id:  # Product has UoS defined
+            # We cannot convert directly between units even if the units are of the same category
+            # as we need to apply the conversion coefficient which is valid only between quantities
+            # in product's default UoM/UoS
+            qty_default_uom = uom_obj._compute_qty_obj(cr, uid, uom, qty, product.uom_id)  # qty in product's default UoM
+            qty_default_uos = qty_default_uom * product.uos_coeff
+            return uom_obj._compute_qty_obj(cr, uid, product.uos_id, qty_default_uos, uos)
+        else:
+            return uom_obj._compute_qty_obj(cr, uid, uom, qty, uos)
+
+
 
 class product_packaging(osv.osv):
     _name = "product.packaging"
index 850189a..4a1ce43 100644 (file)
@@ -1,5 +1,6 @@
-from . import test_uom
+from . import test_uom, test_pricelist
 
 fast_suite = [
        test_uom,
+       test_pricelist
 ]
diff --git a/addons/product/tests/test_pricelist.py b/addons/product/tests/test_pricelist.py
new file mode 100644 (file)
index 0000000..4e61cb1
--- /dev/null
@@ -0,0 +1,70 @@
+from openerp.tests.common import TransactionCase
+
+class TestPricelist(TransactionCase):
+    """Tests for unit of measure conversion"""
+
+    def setUp(self):
+        super(TestPricelist, self).setUp()
+        cr, uid, context = self.cr, self.uid, {}
+        self.ir_model_data = self.registry('ir.model.data')
+        self.product_product = self.registry('product.product')
+        self.product_pricelist = self.registry('product.pricelist')
+        self.uom = self.registry('product.uom')
+
+        self.usb_adapter_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_48')[1]
+        self.datacard_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_46')[1]
+        self.unit_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_uom_unit')[1]
+        self.dozen_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_uom_dozen')[1]
+
+        self.public_pricelist_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'list0')[1]
+        self.sale_pricelist_id = self.product_pricelist.create(cr, uid, {
+            'name': 'Sale pricelist',
+            'type': 'sale',
+            'version_id': [(0, 0, {
+                'name': 'v1.0',
+                'items_id': [(0, 0, {
+                    'name': 'Discount 10%',
+                    'base': 1, # based on public price
+                    'price_discount': -0.1,
+                    'product_id': self.usb_adapter_id
+                }), (0, 0, {
+                    'name': 'Discount -0.5',
+                    'base': 1, # based on public price
+                    'price_surcharge': -0.5,
+                    'product_id': self.datacard_id
+                })]
+            })]
+        }, context=context)
+
+    def test_10_discount(self):
+        # Make sure the price using a pricelist is the same than without after
+        # applying the computation manually
+        cr, uid, context = self.cr, self.uid, {}
+
+        public_context = dict(context, pricelist=self.public_pricelist_id)
+        pricelist_context = dict(context, pricelist=self.sale_pricelist_id)
+
+        usb_adapter_without_pricelist = self.product_product.browse(cr, uid, self.usb_adapter_id, context=public_context)
+        usb_adapter_with_pricelist = self.product_product.browse(cr, uid, self.usb_adapter_id, context=pricelist_context)
+        self.assertEqual(usb_adapter_with_pricelist.price, usb_adapter_without_pricelist.price*0.9)
+
+        datacard_without_pricelist = self.product_product.browse(cr, uid, self.datacard_id, context=public_context)
+        datacard_with_pricelist = self.product_product.browse(cr, uid, self.datacard_id, context=pricelist_context)
+        self.assertEqual(datacard_with_pricelist.price, datacard_without_pricelist.price-0.5)
+
+        # Make sure that changing the unit of measure does not break the unit
+        # price (after converting)
+        unit_context = dict(context,
+            pricelist=self.sale_pricelist_id,
+            uom=self.unit_id)
+        dozen_context = dict(context,
+            pricelist=self.sale_pricelist_id,
+            uom=self.dozen_id)
+
+        usb_adapter_unit = self.product_product.browse(cr, uid, self.usb_adapter_id, context=unit_context)
+        usb_adapter_dozen = self.product_product.browse(cr, uid, self.usb_adapter_id, context=dozen_context)
+        self.assertAlmostEqual(usb_adapter_unit.price*12, usb_adapter_dozen.price)
+
+        datacard_unit = self.product_product.browse(cr, uid, self.datacard_id, context=unit_context)
+        datacard_dozen = self.product_product.browse(cr, uid, self.datacard_id, context=dozen_context)
+        self.assertAlmostEqual(datacard_unit.price*12, datacard_dozen.price)
index c27bb86..33d777f 100644 (file)
@@ -43,6 +43,7 @@ class sale_order_line(osv.osv):
             fiscal_position=False, flag=False, context=None):
 
         def get_real_price(res_dict, product_id, qty, uom, pricelist):
+            """Retrieve the price before applying the pricelist"""
             item_obj = self.pool.get('product.pricelist.item')
             price_type_obj = self.pool.get('product.price.type')
             product_obj = self.pool.get('product.product')
@@ -58,9 +59,8 @@ class sale_order_line(osv.osv):
 
             factor = 1.0
             if uom and uom != product.uom_id.id:
-                product_uom_obj = self.pool.get('product.uom')
-                uom_data = product_uom_obj.browse(cr, uid,  product.uom_id.id)
-                factor = uom_data.factor
+                # the unit price is in a different uom
+                factor = self.pool['product.uom']._compute_qty(cr, uid, uom, 1.0, product.uom_id.id)
             return product_read[field_name] * factor
 
 
@@ -72,12 +72,12 @@ class sale_order_line(osv.osv):
         result=res['value']
         pricelist_obj=self.pool.get('product.pricelist')
         product_obj = self.pool.get('product.product')
-        if product and pricelist:
+        if product and pricelist and self.pool.get('res.users').has_group(cr, uid, 'sale.group_discount_per_so_line'):
             if result.get('price_unit',False):
                 price=result['price_unit']
             else:
                 return res
-
+            uom = result.get('product_uom', uom)
             product = product_obj.browse(cr, uid, product, context)
             pricelist_context = dict(context, uom=uom, date=date_order)
             list_price = pricelist_obj.price_rule_get(cr, uid, [pricelist],
index c719707..d22c0c1 100644 (file)
                                     <field name="date_planned"/>
                                     <field name="company_id" groups="base.group_multi_company" widget="selection"/>
                                     <field name="account_analytic_id" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
-                                    <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,parent.state,context)"/>
-                                    <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,parent.state,context)"/>
+                                    <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
+                                    <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
                                     <field name="price_unit"/>
                                     <field name="taxes_id" widget="many2many_tags" domain="[('parent_id','=',False),('type_tax_use','!=','sale')]"/>
                                     <field name="price_subtotal"/>
                     <sheet>
                         <group>
                             <group>
-                                <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,'draft',context)"/>
+                                <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)"/>
                                 <label for="product_qty"/>
                                 <div>
-                                    <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,'draft',context)" class="oe_inline"/>
-                                    <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,'draft',context)" class="oe_inline"/>
+                                    <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)" class="oe_inline"/>
+                                    <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)" class="oe_inline"/>
                                 </div>
                                 <field name="price_unit"/>
                             </group>
             <field name="model">product.template</field>
             <field name="inherit_id" ref="product.product_template_search_view"/>
             <field name="arch" type="xml">
-                <filter name="filter_to_sell" position="before">
-                    <filter name="filter_to_purchase" string="To Purchase" icon="terp-accessories-archiver+" domain="[('purchase_ok', '=', 1)]"/>
-                </filter>
                 <filter name="filter_to_sell" position="after">
                    <filter name="filter_to_purchase" string="Can be Purchased" icon="terp-accessories-archiver+" domain="[('purchase_ok', '=', 1)]"/>
                 </filter>
index 91486c0..fd199a6 100644 (file)
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
 <data>
-<template id="report_purchaseorder">
-    <t t-call="report.html_container">
-        <t t-foreach="docs" t-as="o">
-            <t t-call="report.external_layout">
-                <div class="page">
-                    <div class="oe_structure"/>
-                    <div class="row">
-                        <div class="col-xs-6">
-                            Shipping address :<br/>
-                            <div t-if="o.dest_address_id">
-                                <div t-field="o.dest_address_id" 
-                                    t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
-                                <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
-                            </div>
+<template id="report_purchaseorder_document">
+    <t t-call="report.external_layout">
+        <div class="page">
+            <div class="oe_structure"/>
+            <div class="row">
+                <div class="col-xs-6">
+                    Shipping address :<br/>
+                    <div t-if="o.dest_address_id">
+                        <div t-field="o.dest_address_id"
+                            t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
+                        <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+                    </div>
 
-                            <div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
-                                <span t-field="o.picking_type_id.warehouse_id.name"/>
-                                <div t-field="o.picking_type_id.warehouse_id.partner_id" 
-                                    t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
-                                <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
-                            </div>
-                        </div>
-                        <div class="col-xs-5 col-xs-offset-1">
-                            <div t-field="o.partner_id" 
-                                t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
-                        </div>
+                    <div t-if="o.picking_type_id and o.picking_type_id.warehouse_id">
+                        <span t-field="o.picking_type_id.warehouse_id.name"/>
+                        <div t-field="o.picking_type_id.warehouse_id.partner_id"
+                            t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
+                        <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
                     </div>
+                </div>
+                <div class="col-xs-5 col-xs-offset-1">
+                    <div t-field="o.partner_id"
+                        t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
+                </div>
+            </div>
 
-                    <h2 t-if="o.state != 'draft'">Purchase Order Confirmation N°<span t-field="o.name"/></h2>
-                    <h2 t-if="o.state == 'draft'">Request for Quotation N°<span t-field="o.name"/></h2>
+            <h2 t-if="o.state != 'draft'">Purchase Order Confirmation N°<span t-field="o.name"/></h2>
+            <h2 t-if="o.state == 'draft'">Request for Quotation N°<span t-field="o.name"/></h2>
 
-                    <div class="row mt32 mb32">
-                        <div t-if="o.name" class="col-xs-3">
-                            <strong>Our Order Reference:</strong>
-                            <p t-field="o.name"/>
-                        </div>
-                        <div t-if="o.partner_ref" class="col-xs-3">
-                            <strong>Your Order Reference</strong>
-                            <p t-field="o.partner_ref"/>
-                        </div>
-                        <div t-if="o.date_order" class="col-xs-3">
-                            <strong>Order Date:</strong>
-                            <p t-field="o.date_order"/>
-                        </div>
-                        <div t-if="o.validator" class="col-xs-3">
-                            <strong>Validated By:</strong>
-                            <p t-field="o.validator"/>
-                        </div>
-                    </div>
+            <div class="row mt32 mb32">
+                <div t-if="o.name" class="col-xs-3">
+                    <strong>Our Order Reference:</strong>
+                    <p t-field="o.name"/>
+                </div>
+                <div t-if="o.partner_ref" class="col-xs-3">
+                    <strong>Your Order Reference</strong>
+                    <p t-field="o.partner_ref"/>
+                </div>
+                <div t-if="o.date_order" class="col-xs-3">
+                    <strong>Order Date:</strong>
+                    <p t-field="o.date_order"/>
+                </div>
+                <div t-if="o.validator" class="col-xs-3">
+                    <strong>Validated By:</strong>
+                    <p t-field="o.validator"/>
+                </div>
+            </div>
 
+            <table class="table table-condensed">
+                <thead>
+                    <tr>
+                        <th><strong>Description</strong></th>
+                        <th><strong>Taxes</strong></th>
+                        <th class="text-center"><strong>Date Req.</strong></th>
+                        <th class="text-right"><strong>Qty</strong></th>
+                        <th class="text-right"><strong>Unit Price</strong></th>
+                        <th class="text-right"><strong>Net Price</strong></th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr t-foreach="o.order_line" t-as="line">
+                        <td>
+                            <span t-field="line.name"/>
+                        </td>
+                        <td>
+                            <span t-esc="', '.join(map(lambda x: x.name, line.taxes_id))"/>
+                        </td>
+                        <td class="text-center">
+                            <span t-field="line.date_planned"/>
+                        </td>
+                        <td class="text-right">
+                            <span t-field="line.product_qty"/>
+                            <span t-field="line.product_uom.name" groups="product.group_uom"/>
+                        </td>
+                        <td class="text-right">
+                            <span t-field="line.price_unit"/>
+                        </td>
+                        <td class="text-right">
+                            <span t-field="line.price_subtotal"
+                                t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+
+            <div class="row">
+                <div class="col-xs-4 pull-right">
                     <table class="table table-condensed">
-                        <thead>
-                            <tr>
-                                <th><strong>Description</strong></th>
-                                <th><strong>Taxes</strong></th>
-                                <th class="text-center"><strong>Date Req.</strong></th>
-                                <th class="text-right"><strong>Qty</strong></th>
-                                <th class="text-right"><strong>Unit Price</strong></th>
-                                <th class="text-right"><strong>Net Price</strong></th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            <tr t-foreach="o.order_line" t-as="line">
-                                <td>
-                                    <span t-field="line.name"/>
-                                </td>
-                                <td>
-                                    <span t-esc="', '.join(map(lambda x: x.name, line.taxes_id))"/>
-                                </td>
-                                <td class="text-center">
-                                    <span t-field="line.date_planned"/>
-                                </td>
-                                <td class="text-right">
-                                    <span t-field="line.product_qty"/>
-                                    <span t-field="line.product_uom.name" groups="product.group_uom"/>
-                                </td>
-                                <td class="text-right">
-                                    <span t-field="line.price_unit"/>
-                                </td>
-                                <td class="text-right">
-                                    <span t-field="line.price_subtotal"
-                                        t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
-                                </td>
-                            </tr>
-                        </tbody>
+                        <tr class="border-black">
+                            <td><strong>Total Without Taxes</strong></td>
+                            <td class="text-right">
+                                <span t-field="o.amount_untaxed"
+                                    t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>Taxes</td>
+                            <td class="text-right">
+                                <span t-field="o.amount_tax"
+                                    t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
+                            </td>
+                        </tr>
+                        <tr class="border-black">
+                            <td><strong>Total</strong></td>
+                            <td class="text-right">
+                                <span t-field="o.amount_total"
+                                    t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
+                            </td>
+                        </tr>
                     </table>
-
-                    <div class="row">
-                        <div class="col-xs-4 pull-right">
-                            <table class="table table-condensed">
-                                <tr class="border-black">
-                                    <td><strong>Total Without Taxes</strong></td>
-                                    <td class="text-right">
-                                        <span t-field="o.amount_untaxed"
-                                            t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
-                                    </td>
-                                </tr>
-                                <tr>
-                                    <td>Taxes</td>
-                                    <td class="text-right">
-                                        <span t-field="o.amount_tax"
-                                            t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
-                                    </td>
-                                </tr>
-                                <tr class="border-black">
-                                    <td><strong>Total</strong></td>
-                                    <td class="text-right">
-                                        <span t-field="o.amount_total"
-                                            t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
-                                    </td>
-                                </tr>
-                            </table>
-                        </div>
-                    </div>
-                
-                    <p t-field="o.notes"/>
-                    <div class="oe_structure"/>
                 </div>
-            </t>
+            </div>
+
+            <p t-field="o.notes"/>
+            <div class="oe_structure"/>
+        </div>
+    </t>
+</template>
+
+<template id="report_purchaseorder">
+    <t t-call="report.html_container">
+        <t t-foreach="doc_ids" t-as="doc_id">
+            <t t-raw="translate_doc(doc_id, doc_model, 'partner_id.lang', 'purchase.report_purchaseorder_document')"/>
         </t>
     </t>
 </template>
index 6abd64b..60f1081 100644 (file)
@@ -1,67 +1,71 @@
 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
 <data>
-<template id="report_purchasequotation">
-    <t t-call="report.html_container">
-        <t t-foreach="docs" t-as="o">
-            <t t-call="report.external_layout">
-                <div class="page">
-                    <div class="oe_structure"/>
+<template id="report_purchasequotation_document">
+    <t t-call="report.external_layout">
+        <div class="page">
+            <div class="oe_structure"/>
 
-                    <div class="row mt32 mb32">
-                        <div class="col-xs-6">
-                            Shipping address :<br/>
-                            <div t-if="o.dest_address_id">
-                                <div t-field="o.dest_address_id" 
-                                    t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
-                                <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
-                            </div>
-                            <div t-if="o.picking_type_id.warehouse_id">
-                                <span t-field="o.picking_type_id.warehouse_id.name"/>
-                                <div t-field="o.picking_type_id.warehouse_id.partner_id" 
-                                    t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
-                                <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
-                            </div>
-                        </div>
-                        <div class="col-xs-5 col-xs-offset-1">
-                            <div t-field="o.partner_id" 
-                                t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
-                        </div>
+            <div class="row mt32 mb32">
+                <div class="col-xs-6">
+                    Shipping address :<br/>
+                    <div t-if="o.dest_address_id">
+                        <div t-field="o.dest_address_id"
+                            t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
+                        <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+                    </div>
+                    <div t-if="o.picking_type_id.warehouse_id">
+                        <span t-field="o.picking_type_id.warehouse_id.name"/>
+                        <div t-field="o.picking_type_id.warehouse_id.partner_id"
+                            t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
+                        <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
                     </div>
+                </div>
+                <div class="col-xs-5 col-xs-offset-1">
+                    <div t-field="o.partner_id"
+                        t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
+                </div>
+            </div>
 
-                    <h2>Request for Quotation <span t-field="o.name"/></h2>
+            <h2>Request for Quotation <span t-field="o.name"/></h2>
 
-                    <table class="table table-condensed">
-                        <thead>
-                            <tr>
-                                <th><strong>Description</strong></th>
-                                <th class="text-center"><strong>Expected Date</strong></th>
-                                <th class="text-right"><strong>Qty</strong></th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            <tr t-foreach="o.order_line" t-as="order_line">
-                                <td>
-                                    <span t-field="order_line.name"/>
-                                </td>
-                                <td class="text-center">
-                                    <span t-field="order_line.date_planned"/>
-                                </td>
-                                <td class="text-right">
-                                    <span t-field="order_line.product_qty"/>
-                                    <span t-field="order_line.product_uom" groups="product.group_uom"/>
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
+            <table class="table table-condensed">
+                <thead>
+                    <tr>
+                        <th><strong>Description</strong></th>
+                        <th class="text-center"><strong>Expected Date</strong></th>
+                        <th class="text-right"><strong>Qty</strong></th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr t-foreach="o.order_line" t-as="order_line">
+                        <td>
+                            <span t-field="order_line.name"/>
+                        </td>
+                        <td class="text-center">
+                            <span t-field="order_line.date_planned"/>
+                        </td>
+                        <td class="text-right">
+                            <span t-field="order_line.product_qty"/>
+                            <span t-field="order_line.product_uom" groups="product.group_uom"/>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
 
-                    <p t-field="o.notes"/>
-                    <span>Regards,</span>
-                    <span t-field="user.signature"/>
+            <p t-field="o.notes"/>
+            <span>Regards,</span>
+            <span t-field="user.signature"/>
 
-                    <div class="oe_structure"/>
-                </div>
-            </t>
+            <div class="oe_structure"/>
+        </div>
+    </t>
+</template>
+
+<template id="report_purchasequotation">
+    <t t-call="report.html_container">
+        <t t-foreach="doc_ids" t-as="doc_id">
+            <t t-raw="translate_doc(doc_id, doc_model, 'partner_id.lang', 'purchase.report_purchasequotation_document')"/>
         </t>
     </t>
 </template>
index cb0f392..088c40e 100644 (file)
@@ -52,7 +52,7 @@ openerp.report = function(instance) {
                 var c = openerp.webclient.crashmanager;
 
                 if (action.report_type == 'qweb-html') {
-                    window.open(report_url, '_blank', 'height=900,width=1280');
+                    window.open(report_url, '_blank', 'scrollbars=1,height=900,width=1280');
                     instance.web.unblockUI();
                 } else if (action.report_type === 'qweb-pdf') {
                     // Trigger the download of the pdf/controller report
index 6666f20..b353287 100644 (file)
@@ -101,7 +101,8 @@ class sale_order_line_make_invoice(osv.osv_memory):
                     flag = False
                     break
             if flag:
-                workflow.trg_validate(uid, 'sale.order', order.id, 'manual_invoice', cr)
+                line.order_id.write({'state': 'progress'})
+                workflow.trg_validate(uid, 'sale.order', order.id, 'all_lines', cr)
 
         if not invoices:
             raise osv.except_osv(_('Warning!'), _('Invoice cannot be created for this Sales Order Line due to one of the following reasons:\n1.The state of this sales order line is either "draft" or "cancel"!\n2.The Sales Order Line is Invoiced!'))
index 5cbb178..b5c1821 100644 (file)
@@ -36,7 +36,11 @@ class sale_order_line(osv.osv):
         frm_cur = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
         to_cur = self.pool.get('product.pricelist').browse(cr, uid, [pricelist])[0].currency_id.id
         if product:
-            purchase_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
+            product = self.pool['product.product'].browse(cr, uid, product, context=context)
+            purchase_price = product.standard_price
+            to_uom = res.get('product_uom', uom)
+            if to_uom != product.uom_id.id:
+                purchase_price = self.pool['product.uom']._compute_price(cr, uid, product.uom_id.id, purchase_price, to_uom)
             ctx = context.copy()
             ctx['date'] = date_order
             price = self.pool.get('res.currency').compute(cr, uid, frm_cur, to_cur, purchase_price, round=False, context=ctx)
index f5be923..49aba8e 100644 (file)
             <t t-foreach="values" t-as="id">
                 <t t-set="file" t-value="widget.data[id]"/>
                 <div class="oe_attachment">
-                    <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
+                    <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="#{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="#{file.name || file.filename}">
                         <span class="oe_fileuploader_in_process">...Upload in progress...</span>
                         <t t-raw="file.name || file.filename"/>
                     </span>
-                    <a t-if="(!file.upload or file.percent_loaded&gt;=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
+                    <a t-if="(!file.upload or file.percent_loaded&gt;=100)" t-att-href="file.url" t-attf-title="#{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
                         <t t-raw="file.name || file.filename"/>
                     </a>
                     <t t-if="(!file.upload or file.percent_loaded&gt;=100)">
-                        <a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
+                        <a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="#{file.id}">[</a>
                     </t>
                 </div>
             </t>
             <t t-foreach="widget.get('value')" t-as="id">
                 <t t-set="file" t-value="widget.data[id]"/>
                 <div>
-                    <a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
+                    <a t-att-href="file.url" t-attf-title="#{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
                         <t t-raw="file.name || file.filename"/>
                     </a>
                 </div>
index b4aefc6..2aa40c3 100644 (file)
@@ -820,7 +820,7 @@ openerp.web_calendar = function(instance) {
             }
             else {
                 var pop = new instance.web.form.FormOpenPopup(this);
-                pop.show_element(this.dataset.model, id, this.dataset.get_context(), {
+                pop.show_element(this.dataset.model, parseInt(id), this.dataset.get_context(), {
                     title: _.str.sprintf(_t("View: %s"),title),
                     view_id: +this.open_popup_action,
                     res_id: id,
index d9a435f..0e89eb1 100644 (file)
@@ -48,7 +48,7 @@ class DiagramView(openerp.http.Controller):
         for tr in transitions:
             list_tr.append(tr)
             connectors.setdefault(tr, {
-                'id': tr,
+                'id': int(tr),
                 's_id': transitions[tr][0],
                 'd_id': transitions[tr][1]
             })
index be5c0a9..8305ddd 100644 (file)
@@ -103,7 +103,7 @@ The order is important: for example if two fields are grouped by row, then the f
 Date/datetime
 -------------
 
-Dates and datetimes are always a little tricky.  There is a special syntax for grouping them by intervals.
+Dates and datetimes are always a little tricky.  There is a special syntax for grouping them by intervals.  Most of the time, the interval can be specified as a suffix:
 
 * field_date:day, 
 * field_date:week, 
@@ -115,13 +115,18 @@ For example,
 
 .. code-block:: xml
 
+        <filter string="Week" context="{'group_by':'date_followup:week'}" help="Week"/>
+
+But to describe a graph view in xml, this would fail the xml validation ("date_followup:week" is not a valid field).  In that case, the graph view can be described with an "interval" attribute.  For example, 
+
+.. code-block:: xml
+
         <graph string="Leads Analysis" type="pivot" stacked="True">
-            <field name="date_deadline:week" type="row"/>
+            <field name="date_deadline" interval="week" type="row"/>
             <field name="stage_id" type="col"/>
             <field name="planned_revenue" type="measure"/>
         </graph>
 
-
 Example:
 --------
 Here is an example of a graph view defined for the model *crm.lead.report*.  It will open in pivot table mode.  If it is switched to bar chart mode, the bars will be stacked.  The data will be grouped according to the date_deadline field in rows, and the columns will be the various stages of an opportunity.  Also, the *planned_revenue* field will be used as a measure.
index 0718a9d..33c5b81 100644 (file)
           t-att-data-oe-company-name="res_company.name">
         <head>
             <meta charset="utf-8" />
-            <t t-if="main_object and 'website_meta_title' in main_object">
+            <t t-if="main_object and 'website_meta_title' in main_object and not title">
                 <t t-set="title" t-value="main_object.website_meta_title"/>
             </t>
-            <t t-if="not title and main_object and 'name' in main_object">
+            <t t-if="main_object and 'name' in main_object and not title and not additional_title">
                 <t t-set="additional_title" t-value="main_object.name"/>
             </t>
             <t t-if="not title">
-                <t t-set="title"><t t-raw="res_company.name"/><t t-if="additional_title"> - <t t-raw="additional_title"/></t></t>
+                <t t-set="title"><t t-if="additional_title"><t t-raw="additional_title"/> | </t><t t-esc="(website or res_company).name"/></t>
             </t>
+
             <meta name="viewport" content="initial-scale=1"/>
             <meta name="description" t-att-content="main_object and 'website_meta_description' in main_object
                 and main_object.website_meta_description or website_meta_description"/>
index 5cb6f82..9ed7fb4 100644 (file)
@@ -92,6 +92,9 @@ class BlogPost(osv.Model):
             'res.users', 'Last Contributor',
             select=True, readonly=True,
         ),
+        'author_avatar': fields.related(
+            'author_id', 'image_small',
+            string="Avatar", type="binary"),
         'visits': fields.integer('No of Views'),
         'ranking': fields.function(_compute_ranking, string='Ranking', type='float'),
     }
index d28ae73..07cf2b8 100644 (file)
                 </div>
 
                 <div t-foreach="blog_posts" t-as="blog_post" class="mb32">
-
-                  <img class="img-circle pull-right mt16"
-                    t-att-src="website.image_url(blog_post.author_id, 'image_small')"
-                    style="width: 50px;"/>
-
+                  <span t-field="blog_post.author_avatar" t-field-options='{"widget": "image", "class": "img-circle pull-right mt16 media-object"}' />
                   <a t-attf-href="/blog/#{ slug(blog_post.blog_id) }/post/#{ slug(blog_post) }">
                       <h2 t-field="blog_post.name" class="mb4"/>
                   </a>
index 830b2bd..1ea72c7 100644 (file)
@@ -184,7 +184,7 @@ class website_sale(http.Controller):
         if category:
             url = "/shop/category/%s" % slug(category)
         pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
-        product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
+        product_ids = product_obj.search(cr, uid, domain, limit=PPG, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
         products = product_obj.browse(cr, uid, product_ids, context=context)
 
         style_obj = pool['product.style']
@@ -367,7 +367,6 @@ class website_sale(http.Controller):
         shipping_ids = []
         checkout = {}
         if not data:
-            print request.uid, request.website.user_id.id
             if request.uid != request.website.user_id.id:
                 checkout.update( self.checkout_parse("billing", partner) )
                 shipping_ids = orm_partner.search(cr, SUPERUSER_ID, [("parent_id", "=", partner.id), ('type', "=", 'delivery')], context=context)
@@ -456,7 +455,7 @@ class website_sale(http.Controller):
         else:
             query = dict((prefix + field_name, getattr(data, field_name))
                 for field_name in all_fields if getattr(data, field_name))
-            if data.parent_id:
+            if address_type == 'billing' and data.parent_id:
                 query[prefix + 'street'] = data.parent_id.name
 
         if query.get(prefix + 'state_id'):
index dda494a..6fbd299 100644 (file)
@@ -7,10 +7,12 @@ $('.oe_website_sale').each(function () {
         var value = +$shippingDifferent.val();
         var data = $shippingDifferent.find("option:selected").data();
         var $snipping = $(".js_shipping", oe_website_sale);
-        var $inputs = $snipping.find("input,select");
+        var $inputs = $snipping.find("input");
+        var $selects = $snipping.find("select");
 
         $snipping.toggle(!!value);
         $inputs.attr("readonly", value <= 0 ? null : "readonly" ).prop("readonly", value <= 0 ? null : "readonly" );
+        $selects.attr("disabled", value <= 0 ? null : "disabled" ).prop("disabled", value <= 0 ? null : "disabled" );
 
         $inputs.each(function () {
             $(this).val( data[$(this).attr("name")] || "" );
index 929d2ef..4de3c36 100644 (file)
                                 t-att-data-shipping_country_id="shipping.country_id and shipping.country_id.id"
                                 ><t t-esc="', '.join('\n'.join(shipping.name_get()[0][1].split(',')).split('\n')[1:])"/></option>
                           </t>
-                          <option value="-1">-- Create a new address --</option>
+                          <option value="-1" t-att-selected="error and len(error) > 0 and shipping_id == -1">-- Create a new address --</option>
                       </select>
                   </div>
               </div>
                   </div>
                   <div t-attf-class="form-group #{error.get('shipping_country_id') and 'has-error' or ''} col-lg-6">
                       <label class="control-label" for="shipping_country_id">Country</label>
-                      <select name="shipping_country_id" class="form-control" t-att-readonly="  'readonly' if shipping_id &gt;= 0 else ''">
+                      <select name="shipping_country_id" class="form-control" t-att-disabled="  'disabled' if shipping_id &gt;= 0 else ''">
                           <option value="">Country...</option>
                           <t t-foreach="countries or []" t-as="country">
                               <option t-att-value="country.id" t-att-selected="country.id == checkout.get('shipping_country_id')"><t t-esc="country.name"/></option>
index ae2c832..2f8f18e 100644 (file)
@@ -11,7 +11,7 @@ ODOO_USER="odoo"
 case "${1}" in
     configure)
         if ! getent passwd | grep -q "^odoo:"; then
-            adduser --system --no-create-home --quiet --group $ODOO_USER
+            adduser --system --home $ODOO_DATA_DIR --quiet --group $ODOO_USER
         fi
         # Register "$ODOO_USER" as a postgres superuser
         su - postgres -c "createuser -s $ODOO_USER" 2> /dev/null || true
@@ -23,7 +23,6 @@ case "${1}" in
         chown $ODOO_USER:$ODOO_GROUP $ODOO_LOG_DIR
         chmod 0750 $ODOO_LOG_DIR
         # Data dir
-        mkdir -p $ODOO_DATA_DIR
         chown $ODOO_USER:$ODOO_GROUP $ODOO_DATA_DIR
         # update-python-modules NOW otherwise invoke-rc.d openerp start will fail
         update-python-modules
index ec6bba3..4c5087b 100644 (file)
@@ -2,7 +2,6 @@ import inspect
 import importlib
 import os.path
 from urlparse import urlunsplit
-import sphinx
 
 def setup(app):
     app.add_config_value('github_user', None, 'env')
@@ -73,13 +72,6 @@ def add_doc_link(app, pagename, templatename, context, doctree):
     if not app.config.github_user and app.config.github_project:
         return
 
-    def github_doc_link(mode='blob'):
-        """ returns the github URL for the current page
-
-        :param str mode: 'edit' for edition view
-        """
-        return make_github_link(
-            app,
-            'doc/%s%s' % (pagename, app.config.source_suffix),
-            mode=mode)
-    context['github_link'] = github_doc_link
+    # can't use functools.partial because 3rd positional is line not mode
+    context['github_link'] = lambda mode='mode': make_github_link(
+        app, 'doc/%s%s' % (pagename, app.config.source_suffix), mode=mode)
index 5e469cf..8abb3fd 100644 (file)
@@ -34,8 +34,8 @@
       {{ toctree(maxdepth=4, collapse=False, includehidden=True,
                  main_navbar=False, titles_only=False) }}
       {% if github_link %}
-        <p><a href="{{ github_link() }}" class="github">
-          View on GitHub
+        <p><a href="{{ github_link(mode='edit') }}" class="github">
+          Edit on GitHub
         </a></p>
       {% endif %}
     </div>
@@ -55,7 +55,7 @@
         <span class="icon-bar"></span>
         <span class="icon-bar"></span>
       </button>
-      <a href="http://odoo.com" class="o_logo navbar-brand">
+      <a href="{{ pathto(master_doc) }}" class="o_logo navbar-brand">
         <span class="o_logo_main">odoo</span><span class="o_logo_app">doc</span>
       </a>
       {% if versions %}
@@ -86,9 +86,9 @@
           {%- if show_copyright %}
             <li>
               {%- if hasdoc('copyright') %}
-                {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+                {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> <a href="https://odoo.com">{{ copyright }}</a>.{% endtrans %}
               {%- else %}
-                {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+                {% trans copyright=copyright|e %}&copy; Copyright <a href="https://odoo.com">{{ copyright }}</a>.{% endtrans %}
               {%- endif %}
             </li>
           {%- endif %}
       </p>
       </div>
     </div>
+    {%- if google_analytics_key -%}
+    <script>
+    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+    ga('create', '{{ google_analytics_key }}', 'auto');
+    ga('send','pageview');
+    </script>
+    {%- endif -%}
 {%- endblock %}
index 9ae72b9..0b86706 100644 (file)
@@ -46,7 +46,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'odoo'
-copyright = u'2014, OpenERP s.a.'
+copyright = u'OpenERP S.A.'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -55,7 +55,7 @@ copyright = u'2014, OpenERP s.a.'
 # The short X.Y version.
 version = '8.0'
 # The full version, including alpha/beta/rc tags.
-release = '8.0b1'
+release = '8.0'
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
@@ -187,6 +187,9 @@ def setup(app):
     app.connect('html-page-context', versionize)
     app.add_config_value('versions', '', 'env')
 
+    app.connect('html-page-context', analytics)
+    app.add_config_value('google_analytics_key', False, 'env')
+
 def canonicalize(app, pagename, templatename, context, doctree):
     """ Adds a 'canonical' URL for the current document in the rendering
     context. Requires the ``canonical_root`` setting being set. The canonical
@@ -212,6 +215,12 @@ def versionize(app, pagename, templatename, context, doctree):
         if vs != app.config.version
     ]
 
+def analytics(app, pagename, templatename, context, doctree):
+    if not app.config.google_analytics_key:
+        return
+
+    context['google_analytics_key'] = app.config.google_analytics_key
+
 def _build_url(root, branch, pagename):
     return "{canonical_url}{canonical_branch}/{canonical_page}".format(
         canonical_url=root,
diff --git a/doc/guides.rst b/doc/guides.rst
deleted file mode 100644 (file)
index 00fea8d..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-======
-Guides
-======
-
-.. toctree::
-    :titlesonly:
-
-    guides/forms
-    guides/themes
-    guides/snippets
-    guides/workflows
-    guides/deployment
diff --git a/doc/guides/deployment.rst b/doc/guides/deployment.rst
deleted file mode 100644 (file)
index 65b68fa..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-.. _guides/deployment:
-
-=======================
-Deploying to production
-=======================
diff --git a/doc/guides/forms.rst b/doc/guides/forms.rst
deleted file mode 100644 (file)
index da09d63..0000000
+++ /dev/null
@@ -1,327 +0,0 @@
-.. highlight:: xml
-
-.. _form-view-guidelines:
-
-Form Views Guidelines
-=====================
-
-.. sectionauthor:: Aline Preillon, Raphael Collet
-
-This document presents functional and technical guidelines for
-creating/organizing form views in Odoo. For each item, both the functional and
-technical aspects are explained. The goal of the new style of forms is to make
-Odoo easier to use, and to guide users through the system.
-
-Business Views
---------------
-
-Business views are targeted at regular users, not advanced users.  Examples
-are: Opportunities, Products, Partners, Tasks, Projects, etc.
-
-.. image:: forms/oppreadonly.png
-   :class: img-responsive
-
-In general, a business view is composed of
-
-1. a status bar on top (with technical or business flow),
-2. a sheet in the middle (the form itself),
-3. a bottom part with History and Comments.
-
-Technically, the new form views are structured as follows in XML::
-
-    <form>
-        <header> ... content of the status bar  ... </header>
-        <sheet>  ... content of the sheet       ... </sheet>
-        <div class="oe_chatter"> ... content of the bottom part ... </div>
-    </form>
-
-The Status Bar
-''''''''''''''
-
-The purpose of the status bar is to show the status of the current record and
-the action buttons.
-
-.. image:: forms/status.png
-   :class: img-responsive
-
-The Buttons
-...........
-
-The order of buttons follows the business flow. For instance, in a sale order,
-the logical steps are:
-
-1. Send the quotation
-2. Confirm the quotation
-3. Create the final invoice
-4. Send the goods
-
-Highlighted buttons (in red by default) emphasize the logical next step, to
-help the user. It is usually the first active button. On the other hand,
-:guilabel:`cancel` buttons *must* remain grey (normal).  For instance, in
-Invoice the button :guilabel:`Refund` must never be red.
-
-Technically, buttons are highlighted by adding the class "oe_highlight"::
-
-    <button class="oe_highlight" name="..." type="..." states="..."/>
-
-The Status
-..........
-
-Uses the ``statusbar`` widget, and shows the current state in red. States
-common to all flows (for instance, a sale order begins as a quotation, then we
-send it, then it becomes a full sale order, and finally it is done) should be
-visible at all times but exceptions or states depending on particular sub-flow
-should only be visible when current.
-
-.. image:: forms/status1.png
-   :class: img-responsive
-
-.. image:: forms/status2.png
-   :class: img-responsive
-
-The states are shown following the order used in the field (the list in a
-selection field, etc). States that are always visible are specified with the
-attribute ``statusbar_visible``.
-
-``statusbar_colors`` can be used to give a custom color to specific states.
-
-::
-
-    <field name="state" widget="statusbar"
-        statusbar_visible="draft,sent,progress,invoiced,done"
-        statusbar_colors="{'shipping_except':'red','waiting_date':'blue'}"/>
-
-The Sheet
-'''''''''
-
-All business views should look like a printed sheet:
-
-.. image:: forms/sheet.png
-   :class: img-responsive
-
-1. Elements inside a ``<form>`` or ``<page>`` do not define groups, elements
-   inside them are laid out according to normal HTML rules. They content can
-   be explicitly grouped using ``<group>`` or regular ``<div>`` elements.
-2. By default, the element ``<group>`` defines two columns inside, unless an
-   attribute ``col="n"`` is used.  The columns have the same width (1/n th of
-   the group's width). Use a ``<group>`` element to produce a column of fields.
-3. To give a title to a section, add a ``string`` attribute to a ``<group>`` element::
-
-     <group string="Time-sensitive operations">
-
-   this replaces the former use of ``<separator string="XXX"/>``.
-4. The ``<field>`` element does not produce a label, except as direct children
-   of a ``<group>`` element\ [#backwards-compatibility]_.  Use :samp:`<label
-   for="{field_name}>` to produce a label of a field.
-
-Sheet Headers
-.............
-
-Some sheets have headers with one or more fields, and the labels of those
-fields are only shown in edit mode.
-
-.. list-table::
-   :header-rows: 1
-   
-   * - View mode
-     - Edit mode
-   * - .. image:: forms/header.png
-          :class: img-responsive
-     - .. image:: forms/header2.png
-          :class: img-responsive
-
-Use HTML text, ``<div>``, ``<h1>``, ``<h2>``… to produce nice headers, and
-``<label>`` with the class ``oe_edit_only`` to only display the field's label
-in edit mode. The class ``oe_inline`` will make fields inline (instead of
-blocks): content following the field will be displayed on the same line rather
-than on the line below it. The form above is produced by the following XML::
-
-    <label for="name" class="oe_edit_only"/>
-    <h1><field name="name"/></h1>
-
-    <label for="planned_revenue" class="oe_edit_only"/>
-    <h2>
-        <field name="planned_revenue" class="oe_inline"/>
-        <field name="company_currency" class="oe_inline oe_edit_only"/> at 
-        <field name="probability" class="oe_inline"/> % success rate
-    </h2>
-
-Button Box
-..........
-
-Many relevant actions or links can be displayed in the form. For example, in
-Opportunity form, the actions "Schedule a Call" and "Schedule a Meeting" take
-an important place in the use of the CRM. Instead of placing them in the
-"More" menu, put them directly in the sheet as buttons (on the top right) to
-make them more visible and more easily accessible.
-
-.. image:: forms/header3.png
-   :class: img-responsive
-
-Technically, the buttons are placed inside a <div> to group them as a block on
-the right-hand side of the sheet.
-
-::
-
-    <div class="oe_button_box oe_right">
-        <button string="Schedule/Log Call" name="..." type="action"/>
-        <button string="Schedule Meeting" name="action_makeMeeting" type="object"/>
-    </div>
-
-Groups and Titles
-.................
-
-A column of fields is now produced with a ``<group>`` element, with an
-optional title.
-
-.. image:: forms/screenshot-03.png
-   :class: img-responsive
-
-::
-
-    <group string="Payment Options">
-        <field name="writeoff_amount"/>
-        <field name="payment_option"/>
-    </group>
-
-It is recommended to have two columns of fields on the form. For this, simply
-put the ``<group>`` elements that contain the fields inside a top-level
-``<group>`` element.
-
-To make :ref:`view extension <reference/views/inheritance>` simpler, it is
-recommended to put a ``name`` attribute on ``<group>`` elements, so new fields
-can easily be added at the right place.
-
-Special Case: Subtotals
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Some classes are defined to render subtotals like in invoice forms:
-
-.. image:: forms/screenshot-00.png
-   :class: img-responsive
-
-::
-
-    <group class="oe_subtotal_footer">
-        <field name="amount_untaxed"/>
-        <field name="amount_tax"/>
-        <field name="amount_total" class="oe_subtotal_footer_separator"/>
-        <field name="residual" style="margin-top: 10px"/>
-    </group>
-
-Placeholders and Inline Fields
-..............................
-
-Sometimes field labels make the form too complex. One can omit field labels,
-and instead put a placeholder inside the field. The placeholder text is
-visible only when the field is empty. The placeholder should tell what to
-place inside the field, it *must not* be an example as they are often confused
-with filled data.
-
-One can also group fields together by rendering them "inline" inside an
-explicit block element like `<div>``. This allows grouping semantically
-related fields as if they were a single (composite) fields.
-
-The following example, taken from the *Leads* form, shows both placeholders and
-inline fields (zip and city).
-
-.. list-table::
-   :header-rows: 1
-
-   * - Edit mode
-     - View mode
-   * - .. image:: forms/placeholder.png
-          :class: img-responsive
-     - .. image:: forms/screenshot-01.png
-          :class: img-responsive
-
-::
-
-    <group>
-        <label for="street" string="Address"/>
-        <div>
-            <field name="street" placeholder="Street..."/>
-            <field name="street2"/>
-            <div>
-                <field name="zip" class="oe_inline" placeholder="ZIP"/>
-                <field name="city" class="oe_inline" placeholder="City"/>
-            </div>
-            <field name="state_id" placeholder="State"/>
-            <field name="country_id" placeholder="Country"/>
-        </div>
-    </group>
-
-Images
-......
-
-Images, like avatars, should be displayed on the right of the sheet.  The
-product form looks like:
-
-.. image:: forms/screenshot-02.png
-   :class: img-responsive
-
-The form above contains a <sheet> element that starts with::
-
-    <field name="product_image" widget="image" class="oe_avatar oe_right"/>
-
-Tags
-....
-
-Most :class:`~openerp.fields.Many2many` fields, like categories, are better
-rendered as a list of tags. Use the widget ``many2many_tags`` for this:
-
-.. image:: forms/screenshot-04.png
-   :class: img-responsive
-
-::
-
-    <field name="category_id"
-        widget="many2many_tags"/>
-
-Task-based forms
-----------------
-
-Configuration Forms
-'''''''''''''''''''
-
-Examples of configuration forms: Stages, Leave Type, etc.  This concerns all
-menu items under Configuration of each application (like Sales/Configuration).
-
-.. image:: forms/nosheet.png
-   :class: img-responsive
-
-For those views, the guidelines are:
-
-1. no header (because no state, no workflow, no button)
-2. no sheet
-
-Regular Wizards (dialog)
-''''''''''''''''''''''''
-
-Example: "Schedule a Call" from an opportunity.
-
-.. image:: forms/wizard-popup.png
-   :class: img-responsive
-
-The guidelines are:
-
-1. avoid separators (the title is already in the popup title bar, so another
-   separator is not relevant)
-2. avoid cancel buttons (user generally close the popup window to get the same
-   effect)
-3. action buttons must be highlighted (red)
-4. when there is a text area, use a placeholder instead of a label or a
-   separator
-5. like in regular form views, put buttons in the <header> element
-
-Configuration Wizard
-''''''''''''''''''''
-
-Example: Settings / Configuration / Sales.  The guidelines are:
-
-1. always in line (no popup)
-2. no sheet
-3. keep the cancel button (users cannot close the window)
-4. the button "Apply" must be red
-
-.. [#backwards-compatibility] for backwards compatibility reasons
diff --git a/doc/guides/forms/header.png b/doc/guides/forms/header.png
deleted file mode 100644 (file)
index bf97b8c..0000000
Binary files a/doc/guides/forms/header.png and /dev/null differ
diff --git a/doc/guides/forms/header2.png b/doc/guides/forms/header2.png
deleted file mode 100644 (file)
index e6b163b..0000000
Binary files a/doc/guides/forms/header2.png and /dev/null differ
diff --git a/doc/guides/forms/header3.png b/doc/guides/forms/header3.png
deleted file mode 100644 (file)
index 17e8fb9..0000000
Binary files a/doc/guides/forms/header3.png and /dev/null differ
diff --git a/doc/guides/forms/nosheet.png b/doc/guides/forms/nosheet.png
deleted file mode 100644 (file)
index 99d915c..0000000
Binary files a/doc/guides/forms/nosheet.png and /dev/null differ
diff --git a/doc/guides/forms/oppreadonly.png b/doc/guides/forms/oppreadonly.png
deleted file mode 100644 (file)
index 447e7bb..0000000
Binary files a/doc/guides/forms/oppreadonly.png and /dev/null differ
diff --git a/doc/guides/forms/placeholder.png b/doc/guides/forms/placeholder.png
deleted file mode 100644 (file)
index ab7d592..0000000
Binary files a/doc/guides/forms/placeholder.png and /dev/null differ
diff --git a/doc/guides/forms/screenshot-00.png b/doc/guides/forms/screenshot-00.png
deleted file mode 100644 (file)
index dca05af..0000000
Binary files a/doc/guides/forms/screenshot-00.png and /dev/null differ
diff --git a/doc/guides/forms/screenshot-01.png b/doc/guides/forms/screenshot-01.png
deleted file mode 100644 (file)
index 464a237..0000000
Binary files a/doc/guides/forms/screenshot-01.png and /dev/null differ
diff --git a/doc/guides/forms/screenshot-02.png b/doc/guides/forms/screenshot-02.png
deleted file mode 100644 (file)
index ea6ec77..0000000
Binary files a/doc/guides/forms/screenshot-02.png and /dev/null differ
diff --git a/doc/guides/forms/screenshot-03.png b/doc/guides/forms/screenshot-03.png
deleted file mode 100644 (file)
index 3a59801..0000000
Binary files a/doc/guides/forms/screenshot-03.png and /dev/null differ
diff --git a/doc/guides/forms/screenshot-04.png b/doc/guides/forms/screenshot-04.png
deleted file mode 100644 (file)
index b0de67d..0000000
Binary files a/doc/guides/forms/screenshot-04.png and /dev/null differ
diff --git a/doc/guides/forms/sheet.png b/doc/guides/forms/sheet.png
deleted file mode 100644 (file)
index dee87a0..0000000
Binary files a/doc/guides/forms/sheet.png and /dev/null differ
diff --git a/doc/guides/forms/status.png b/doc/guides/forms/status.png
deleted file mode 100644 (file)
index 4f4a0cb..0000000
Binary files a/doc/guides/forms/status.png and /dev/null differ
diff --git a/doc/guides/forms/status1.png b/doc/guides/forms/status1.png
deleted file mode 100644 (file)
index 7ccd80b..0000000
Binary files a/doc/guides/forms/status1.png and /dev/null differ
diff --git a/doc/guides/forms/status2.png b/doc/guides/forms/status2.png
deleted file mode 100644 (file)
index b0d01bd..0000000
Binary files a/doc/guides/forms/status2.png and /dev/null differ
diff --git a/doc/guides/forms/wizard-popup.png b/doc/guides/forms/wizard-popup.png
deleted file mode 100644 (file)
index d36549f..0000000
Binary files a/doc/guides/forms/wizard-popup.png and /dev/null differ
diff --git a/doc/guides/snippets.rst b/doc/guides/snippets.rst
deleted file mode 100644 (file)
index 1834ace..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-========
-Snippets
-========
diff --git a/doc/guides/themes.rst b/doc/guides/themes.rst
deleted file mode 100644 (file)
index 9044c47..0000000
+++ /dev/null
@@ -1,474 +0,0 @@
-.. highlight:: xml
-
-===============
-Creating themes
-===============
-
-Basic set up
-============
-
-Create a basic theme module with :command:`odoo.py scaffold` and the ``theme``
-template: from the root odoo folder, use
-
-.. code-block:: console
-
-    $ ./odoo.py scaffold -t theme "Dummy Theme" addons
-
-this should create a new folder ``dummy_theme`` in the ``addons`` directory
-with the structure:
-
-.. code-block:: text
-
-    addons/dummy_theme
-    |-- __init__.py
-    |-- __openerp__.py
-    |-- static
-    |   `-- style
-    |       `-- custom.less
-    `-- views
-        |-- options.xml
-        |-- pages.xml
-        `-- snippets.xml
-
-``static/styles`` contains your stylesheet(s), ``views`` contains the various
-XML files describing the theme and theme features to Odoo.
-
-Static Page
------------
-
-Creating a new template
-'''''''''''''''''''''''
-
-Create a new file :file:`odoo/addons/theme_dummy/views/pages.xml` and open it.
-
-In odoo, a page means a new template. You don't need special skills, simply
-copy paste the lines::
-
-  <template id="website.hello" name="Homepage" page="True">
-      <t t-call="website.layout">
-          <div id="wrap" class="oe_structure oe_empty">
-          </div>
-      </t>
-  </template>
-
-Refresh the page and feel the hit.
-
-Editing content on a page
-'''''''''''''''''''''''''
-
-You can now add you content! You should always use the Bootstrap structure as
-below::
-
-    <template id="website.hello" name="Homepage" page="True">
-        <t t-call="website.layout">
-            <div id="wrap" class="oe_structure oe_empty">
-                <section>
-                    <div class="container">
-                        <div class="row">
-                            <h1>This is Your Content</h1>
-                            <p>Isn't amazing to edit everything inline?</p>
-                            <hr/>
-                        </div>
-                    </div>
-                </section>
-            </div>
-        </t>
-    </template>
-
-Adding new item in the menu
-'''''''''''''''''''''''''''
-
-Adding these few more lines will put the new page in your menu::
-
-  <record id="hello_menu" model="website.menu">
-      <field name="name">Hello</field>
-      <field name="url">/page/hello</field>
-      <field name="parent_id" ref="website.main_menu"/>
-      <field name="sequence" type="int">20</field>
-  </record>
-
-Congrats! It's online! Now drag and drop some snippets on the page and let's
-style!
-
-Pimp Your Theme
----------------
-
-Easy styling with less
-''''''''''''''''''''''
-
-In ``odoo/addons/theme_dummy/static`` create a new folder and name it
-``style``. In the new folder ``odoo/addons/theme_dummy/static/style`` create a
-file and name it ``custom.less``. Open ``custom.less`` in the text editor and
-modify these lines as below:
-
-
-.. code-block:: css
-
-   .h1 {
-       color: #215487;
-   }
-   .span {
-       border: 2px solid black;
-       background-color: #eee;
-   }
-
-Refresh the page and feel the hit.
-
-Get the most of the dom
-'''''''''''''''''''''''
-
-Right-Click, inspect element. You can go deeper by styling the main layout
-container. Here we try with the 'wrapwrap' id.
-
-.. code-block:: css
-
-   #wrapwrap {
-        background-color: #222;
-        width: 80%;
-        margin: 0 auto;
-   }
-
-Easy layout with bootstrap
-''''''''''''''''''''''''''
-
-Open :file:`odoo/addons/theme_dummy/views/pages.xml` and add a new section::
-
-  <section>
-      <div class="container">
-          <div class="row">
-              <div class="alert alert-primary" role="alert">
-                  <a href="#" class="alert-link">...</a>
-              </div>
-              <div class="col-md-6 bg-blue">
-                  <h2>BLUE it!</h2>
-              </div>
-              <div class="col-md-6 bg-green">
-                  <h2>GREEN THAT!</h2>
-              </div>
-          </div>
-      </div>
-  </section>
-
-Refresh the page and check how it looks.
-
-The background of the alert component is the default Bootstrap primary color.
-The two other div your created have no custom styles applied yet.  Open
-:file:`odoo/addons/theme_dummy/static/style/custom.less` and add these lines:
-
-.. code-block:: css
-
-        @brand-primary: #1abc9c;
-        @color-blue: #3498db;
-        @color-green: #2ecc71;
-
-        .bg-blue { background: @color-blue; }
-        .bg-green { background: @color-green; }
-
-        .h2 { color: white; }
-
-As you see, the default primary has changed and your new colors are shining!
-
-Build Your First Snippet
-------------------------
-
-Setting up __openerp__.py
-'''''''''''''''''''''''''
-
-Open ``__openerp__.py`` and add a new line as below:
-
-.. code-block:: python
-
-   {
-       'name': 'Dummy Theme',
-       'description': 'Dummy Theme',
-       'category': 'Website',
-       'version': '1.0',
-       'author': 'OpenERP SA',
-       'depends': ['website'],
-       'data': [
-           'views/snippets.xml',
-       ],
-       'application': True,
-   }
-
-In ``odoo/addons/theme_learn/views`` create a new xml file, name it
-``snippets.xml`` and open it in a text editor
-
-Add your snippet in the menu
-''''''''''''''''''''''''''''
-
-Before typing your html code, you need to locate it in the WEBb. drop-down
-menu.  In this case, we will add it at the end of the Structure section::
-
-  <template id="snippets" inherit_id="website.snippets" name="Clean Theme snippets">
-    <xpath expr="//div[@id='snippet_structure']" position="inside">
-    </xpath>
-  </template>
-
-Now open a new div, do not give it any id or classes. It will contain your
-snippet::
-
-    <xpath expr="//div[@id='snippet_structure']" position="inside">
-        <div>
-        </div>
-    </xpath>
-
-A thumbnail is also needed to create a more attractive link in the menu. You
-can use labels to focus on your themes snippets.  Simply add a new div with
-the class ``oe_snippet_thumbnail`` and add your thumbnail image (100x79px)::
-
-  <xpath expr="//div[@id='snippet_structure']" position="inside">
-      <div>
-          <div class="oe_snippet_thumbnail">
-              <img class="oe_snippet_thumbnail_img" src="/theme_Dummy/static/img/blocks/block_title.png"/>
-              <span class="oe_snippet_thumbnail_title">SNIP IT!</span>
-          </div>
-      </div>
-  </xpath>
-
-And voila! Your new snippet is now ready to use. Just drag and drop it on your
-page to see it in action.
-
-The snippet body
-''''''''''''''''
-
-A snippet has to be in a section with the class ``oe_snippet_body`` to work
-correctly.  As Odoo use the Bootstrap framework, you have use containers and
-rows to hold your content. Please refer the the Bootstrap documentation::
-
-  <xpath expr="//div[@id='snippet_structure']" position="inside">
-      <div>
-          <div class="oe_snippet_thumbnail">
-              <img class="oe_snippet_thumbnail_img" src="/theme_Dummy/static/img/blocks/block_title.png"/>
-              <span class="oe_snippet_thumbnail_title">SNIP IT!</span>
-          </div>
-
-          <section class="oe_snippet_body fw_categories">
-              <div class="container">
-                  <div class="row">
-                  </div>
-              </div>
-          </section>
-      </div>
-  </xpath>
-
-Inside your fresh new row, add some bootstraped contents::
-
-  <div class="col-md-12 text-center mt32 mb32">
-      <h2>A great Title</h2>
-      <h3 class="text-muted ">And a great subtitle too</h3>
-      <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. </p>
-  </div>
-
-
-Adding images to your snippet
-'''''''''''''''''''''''''''''
-
-You can easely add images in your snippets simply by setting up css
-backgrounds images.
-
-In ``odoo/addons/theme_dummy/static/`` create a new folder and name it
-``img``. Put your images there, in sub-folders if needed.  Open
-:file:`odoo/addons/theme_dummy/static/style/custom.less`, add these lines
-
-.. code-block:: css
-
-   @img-01: url("../img/img-boy.png");
-   .dummy-boy { background-image: @img-01; }
-
-   @img-02: url("../img/img-girl.png");
-   .dummy-girl { background-image: @img-02; }
-
-In :file:`odoo/addons/theme_dummy/views/pages.xml` change the correspondant
-lines as below::
-
-    <section>
-        <div class="container">
-            <div class="row dummy-bg">
-                <div class="alert alert-primary" role="alert">
-                <a href="#" class="alert-link">...</a>
-                </div>
-                <div class="col-md-6">
-                <h2>BLUE it!</h2>
-                    <div class="dummy-boy">
-                    </div>
-                </div>
-                <div class="col-md-6">
-                <h2>GREEN THAT!</h2>
-                    <div class="dummy-girl">
-                    </div>
-                </div>
-            </div>
-        </div>
-    </section>
-
-Your new snippet is now ready to use. Just drag and drop it on your page to
-see it in action.
-
-Advanced Customization
-======================
-
-Defining Your Theme Options
----------------------------
-
-Understanding XPath
-'''''''''''''''''''
-
-As your stylesheets are running on the whole website, giving more option to
-your snippets and applying them independently will push your design
-forward. In ``odoo/addons/theme_dummy/views/`` create a new file, name it
-``options.xml`` and add these lines::
-
-    <template id="gourman_website_options_pattern" inherit_id="website.snippet_options">
-        <xpath expr="//div[@data-option='dummy_options']//ul" position="after">
-        </xpath>
-    </template>
-
-Explain xpath
-"""""""""""""
-
-.. TODO:: syntax not correct (see website examples) 
-
-Your option menu is now correctly set in the database, you can create an new dropdown menu.
-
-Let's say yout want three options which will change the text color and the background.
-In option.xml, add these lines inside the xpath::
-
-      <li data-check_class="text-purple"><a>YOUR OPTION 1</a></li>
-      <li class="dropdown-submenu">
-          <a tabindex="-1" href="#">Your sub option</a>
-          <ul class="dropdown-menu">
-            <li data-select_class="bg-yellow"><a>YOUR OPTION 2</a></li>
-            <li data-select_class="text-light-bg-dark"><a>YOUR OPTION 3</a></li>
-            <li data-select_class=""><a>None</a></li>
-          </ul>
-      <li>
-
-Simple less css options
-'''''''''''''''''''''''
-
-In order to see these options in action, you have to write some new css
-classes. Open custom.css and add this new lines
-
-.. code-block:: css
-
-    @color-purple: #2ecc71;
-    @color-yellow: #2ecc71;
-
-    .text-purple { color: @color-purple; }
-    .bg-yellow { background-color: @color-yellow;}
-    .text-light-bg-dark { color: #eee; background-color: #222;}
-
-Refresh the page. Select a snippet and click Customize. Choose one of your new
-options apply it.
-
-XPath & inherits
-''''''''''''''''
-
-You can also add images in your variables and use them on certain part of your
-pages, snippets or any html element.
-
-In :file:`odoo/addons/theme_dummy/static/style/custom.css` add these new lines
-
-.. code:: css
-
-    @bg-01: url("../img/background/bg-blur.jpg");
-
-    .bg-01 {
-        background-image: @bg-01;
-    }
-
-Now that you have set the background image, you can decide how and where the
-user can use it, for example, on a simple div.
-
-Open :file:`odoo/addons/theme_dummy/views/options.xml` and add this new xpath::
-
-  <xpath expr="//div[@data-option='background-dummy']//ul" position="after">
-      <ul class="dropdown-menu">
-          <li data-value="bg-01">
-              <a>Image 1</a>
-          </li>
-      </ul>
-  </xpath>
-
-Your option is ready to be applied but you want it to be shown only a certain
-part of a snippet.
-
-Open :file:`odoo/addons/theme_dummy/views/snippets.xml` and add a new snippet
-with the method we learned previously::
-
-    <xpath expr="//div[@id='snippet_structure']" position="inside">
-        <div>
-        <!-- Add a Thumbnail in the Website Builder drop-down menu -->
-            <div class="oe_snippet_thumbnail">
-                <img class="oe_snippet_thumbnail_img" src="/theme_Dummy/static/img/blocks/block_title.png"/>
-                <span class="oe_snippet_thumbnail_title">Test OPTION</span>
-            </div>
-        <!-- Your Snippet content -->
-            <section class="oe_snippet_body fw_categories">
-                <div class="container">
-                    <div class="row">
-                        <div class="col-md-6 text-center mt32 mb32">
-                            <h2>NO OPTION</h2>
-                            <p>OFF</p>
-                        </div>
-                        <div class="col-md-6 text-center mt32 mb32 test-option">
-                            <h2>OPTION</h2>
-                            <p>This div has the 'test-option' class</p>
-                        </div>
-                    </div>
-                </div>
-            </section>
-        </div>
-    </xpath>
-
-As you see, the second ``col-md`` has a class named ``test-option``.  We are
-going to specify where this option can be turned on by adding the
-``data-selector`` attribute.
-
-Go back to your ``options.xml`` files, add these new lines::
-
-  <xpath expr="//div[@data-option='background-dummy']" position="attributes">
-      <attribute name="data-selector">test-option</attribute>
-  </xpath>
-
-Refresh your browser. You should now be able to add your image background on
-the left div only.  The option is now available on each section but also on
-the left div with the custom class.
-
-The Image Database
-------------------
-
-Modifying the image database
-''''''''''''''''''''''''''''
-
-Odoo provides its own image library but you certainly want to adapt it to your
-design.  Do not use the Media Manager uploading Tool to add image in your
-theme. The images url's will be lost on reload!  Instead of uploading your
-images, you can create your own library and disable the old ones.
-
-In ``odoo/addons/theme_dummy/views/`` create a new file, name it
-``images.xml`` and add these lines::
-
-  <record id="image_bg_blue" model="ir.attachment">
-      <field name="name">bg_blue.jpg</field>
-      <field name="datas_fname">bg_blue.jpg</field>
-      <field name="res_model">ir.ui.view</field>
-      <field name="type">url</field>
-      <field name="url">/theme_clean/static/img/library/bg/bg_blue.jpg</field>
-  </record>
-
-Your images is now available in your Media Manager.  And your Theme has a
-total new look.
-
-Theme Selector
-==============
-
-Set Up
-------
-
-Understanding theme variants
-''''''''''''''''''''''''''''
-
-Combining theme variants
-''''''''''''''''''''''''
diff --git a/doc/guides/workflows.rst b/doc/guides/workflows.rst
deleted file mode 100644 (file)
index 05957fc..0000000
+++ /dev/null
@@ -1,302 +0,0 @@
-.. _guides/workflows:
-
-Workflows
-=========
-
-In OpenERP, a workflow is a technical artefact to manage a set of "things to
-do" associated to the records of some data model. The workflow provides a
-higher- level way to organize the things to do on a record.
-
-More specifically, a workflow is a directed graph where the nodes are called
-"activities" and the arcs are called "transitions".
-
-- Activities define work that should be done within the OpenERP server, such
-  as changing the state of some records, or sending emails.
-- Transitions control how the workflow progresses from activity to activity.
-
-In the definition of a workflow, one can attach conditions, signals, and
-triggers to transitions, so that the behavior of the workflow depends on user
-actions (such as clicking on a button), changes to records, or arbitrary
-Python code.
-
-Basics
-------
-
-Defining a workflow with data files is straightforward: a record "workflow" is
-given together with records for the activities and the transitions. For
-instance, here is a simple sequence of two activities defined in XML
-
-.. code-block:: xml
-
-    <record id="test_workflow" model="workflow">
-        <field name="name">test.workflow</field>
-        <field name="osv">test.workflow.model</field>
-        <field name="on_create">True</field>
-    </record>
-
-    <record id="activity_a" model="workflow.activity">
-        <field name="wkf_id" ref="test_workflow"/>
-        <field name="flow_start">True</field>
-        <field name="name">a</field>
-        <field name="kind">function</field>
-        <field name="action">print_a()</field>
-    </record>
-    <record id="activity_b" model="workflow.activity">
-        <field name="wkf_id" ref="test_workflow"/>
-        <field name="flow_stop">True</field>
-        <field name="name">b</field>
-        <field name="kind">function</field>
-        <field name="action">print_b()</field>
-    </record>
-
-    <record id="trans_a_b" model="workflow.transition">
-        <field name="act_from" ref="activity_a"/>
-        <field name="act_to" ref="activity_b"/>
-    </record>
-
-A worfklow is always defined with respect to a particular model (the model is
-given by the attribute ``osv`` on the model ``workflow``). Methods specified
-in the activities or transitions will be called on that model.
-
-In the example code above, a workflow called "test_workflow" is created. It is
-made up of two activies, named "a" and "b", and one transition, going from "a"
-to "b".
-
-The first activity has its attribute ``flow_start`` set to ``True`` so that
-OpenERP knows where to start the workflow traversal after it is instanciated.
-Because ``on_create`` is set to True on the workflow record, the workflow is
-instanciated for each newly created record. (Otherwise, the workflow should be
-instanciated by other means, such as from some module Python code.)
-
-When the workflow is instanciated, it begins with activity "a". That activity
-is of kind ``function``, which means that the action ``print_a()`` is a method
-call on the model ``test.workflow`` (the usual ``cr, uid, ids, context``
-arguments are passed for you).
-
-The transition between "a" and "b" does not specify any condition. This means
-that the workflow instance immediately goes from "a" to "b" after "a" has been
-processed, and thus also processes activity "b".
-
-Transitions
------------
-
-Transitions provide the control structures to orchestrate a workflow. When an
-activity is completed, the workflow engine tries to get across transitions
-departing from the completed activity, towards the next activities. In their
-simplest form (as in the example above), they link activities sequentially:
-activities are processed as soon as the activities preceding them are
-completed.
-
-Instead of running all activities in one fell swoop, it is also possible to
-wait on transitions, going through them only when some criteria are met. The
-criteria are the conditions, the signals, and the triggers. They are detailed
-in the following sections.
-
-Conditions
-''''''''''
-
-When an activity has been completed, its outgoing transitions are inspected to
-determine whether it is possible for the workflow instance to proceed through
-them and reach the next activities. When only a condition is defined (i.e., no
-signal or trigger is defined), the condition is evaluated by OpenERP, and if
-it evaluates to ``True``, the worklfow instance progresses through the
-transition.  If the condition is not met, it will be reevaluated every time
-the associated record is modified, or by an explicit method call to do it.
-
-By default, the attribute ``condition`` (i.e., the expression to be evaluated)
-is just "True", which trivially evaluates to ``True``. Note that the condition
-may be several lines long; in that case, the value of the last one determines
-whether the transition can be taken.
-
-In the condition evaluation environment, several symbols are conveniently
-defined (in addition to the OpenERP ``safe_eval`` environment):
-
-- all the model column names, and
-- all the browse record's attributes.
-
-Signals
-'''''''
-
-In addition to a condition, a transition can specify a signal name. When such
-a signal name is present, the transition is not taken directly, even if the
-condition evaluates to ``True``. Instead the transition blocks, waiting to be
-woken up.
-
-In order to wake up a transition with a defined signal name, the signal must
-be sent to the workflow instance. A common way to send a signal is to use a
-button in the user interface, using the element ``<button/>`` with the signal
-name as the attribute ``name`` of the button. Once the button is clicked, the
-signal is sent to the workflow instance of the current record.
-
-.. note:: The condition is still evaluated when the signal is sent to the
-          workflow instance.
-
-Triggers
-''''''''
-
-With conditions that evaluate to ``False``, transitions are not taken (and
-thus the activity it leads to is not processed immediately). Still, the
-workflow instance can get new chances to progress across that transition by
-providing so-called triggers. The idea is that when the condition is not
-satisfied, triggers are recorded in database. Later, it is possible to wake up
-specifically the workflow instances that installed those triggers, offering
-them to reevaluate their transition conditions. This mechanism makes it
-cheaper to wake up workflow instances by targetting just a few of them (those
-that have installed the triggers) instead of all of them.
-
-Triggers are recorded in database as record IDs (together with the model name)
-and refer to the workflow instance waiting for those records. The transition
-definition provides a model name (attribute ``trigger_model``) and a Python
-expression (attribute ``trigger_expression``) that evaluates to a list of
-record IDs in the given model. Any of those records can wake up the workflow
-instance they are associated with.
-
-.. note:: triggers are not re-installed whenever the transition is re-tried.
-
-Splitting and joining transitions
-'''''''''''''''''''''''''''''''''
-
-When multiple transitions leave the same activity, or lead to the same
-activity, OpenERP provides some control over which transitions are actually
-taken, or how the reached activity will be processed. The attributes
-``split_mode`` and ``join_mode`` on the activity are used for such
-control. The possible values of those attributes are explained below.
-
-Activities
-----------
-
-While the transitions can be seen as the control structures of the workflows,
-activities are the places where everything happens, from changing record
-states to sending email.
-
-Different kinds of activities exist: ``Dummy``, ``Function``, ``Subflow``, and
-``Stop all``, each doing different things when the activity is processed. In
-addition to their kind, activies have other properties, detailed in the next
-sections.
-
-Flow start and flow stop
-''''''''''''''''''''''''
-
-The attribute ``flow_start`` is a boolean value specifying whether the activity
-is processed when the workflow is instanciated. Multiple activities can have
-their attribute ``flow_start`` set to ``True``. When instanciating a workflow
-for a record, OpenERP simply processes all of them, and evaluate all their
-outgoing transitions afterwards.
-
-The attribute ``flow_stop`` is a boolean value specifying whether the activity
-stops the workflow instance. A workflow instance is considered completed when
-all its activities with the attribute ``flow_stop`` set to ``True`` are
-completed.
-
-It is important for OpenERP to know when a workflow instance is completed. A
-workflow can have an activity that is actually another workflow (called a
-subflow); that activity is completed when the subflow is completed.
-
-Subflow
-'''''''
-
-An activity can embed a complete workflow, called a subflow (the embedding
-workflow is called the parent workflow). The workflow to instanciate is
-specified by attribute ``subflow_id``.
-
-.. note:: In the GUI, that attribute can not be set unless the kind of the
-          activity is ``Subflow``.
-
-The activity is considered completed (and its outgoing transitions ready to be
-evaluated) when the subflow is completed (see attribute ``flow_stop`` above).
-
-Sending a signal from a subflow
-'''''''''''''''''''''''''''''''
-
-When a workflow is embedded in an activity (as a subflow) of a workflow, the
-sublow can send a signal from its own activities to the parent workflow by
-giving a signal name in the attribute ``signal_send``. OpenERP processes those
-activities by sending the value of ``signal_send`` prefixed by "subflow."  to
-the parent workflow instance.
-
-In other words, it is possible to react and get transitions in the parent
-workflow as activities are executed in the sublow.
-
-Server actions
-''''''''''''''
-
-An activity can run a "Server Action" by specifying its ID in the attribute
-``action_id``.
-
-Python action
-'''''''''''''
-
-An activity can execute some Python code, given by the attribute ``action``.
-The evaluation environment is the same as the one explained in the section
-`Conditions`_.
-
-Split mode
-''''''''''
-
-After an activity has been processed, its outgoing transitions are evaluated.
-Normally, if a transition can be taken, OpenERP traverses it and proceed to
-the activity the transition leads to.
-
-Actually, when more than a single transition is leaving an activity, OpenERP
-may proceed or not, depending on the other transitions. That is, the
-conditions on the transitions can be combined together, and the combined
-result instructs OpenERP to traverse zero, one, or all the transitions. The
-way they are combined is controlled by the attribute ``split_mode``.
-
-There are three possible split modes: ``XOR``, ``OR`` and ``AND``.
-
-``XOR``
-    When the transitions are combined with a ``XOR`` split mode, as soon as a
-    transition has a satisfied condition, the transition is traversed and the
-    others are skipped.
-``OR``
-    With the ``OR`` mode, all the transitions with a satisfied condition are
-    traversed. The remaining transitions will not be evaluated later.
-``AND``
-    With the ``AND`` mode, OpenERP will wait for all outgoing transition
-    conditions to be satisfied, then traverse all of them at once.
-
-Join mode
-'''''''''
-
-Just like outgoing transition conditions can be combined together to decide
-whether they can be traversed or not, incoming transitions can be combined
-together to decide if and when an activity may be processed. The attribute
-``join_mode`` controls that behavior.
-
-There are two possible join modes: ``XOR`` and ``AND``.
-
-``XOR``
-    With the ``XOR`` mode, an incoming transition with a satisfied condition
-    is traversed immediately, and enables the processing of the activity.
-
-``AND``
-    With the ``AND`` mode, OpenERP will wait until all incoming transitions
-    have been traversed before enabling the processing of the activity.
-
-Kinds
-'''''
-
-Activities can be of different kinds: ``dummy``, ``function``, ``subflow``, or
-``stopall``. The kind defines what type of work an activity can do.
-
-Dummy
-    The ``dummy`` kind is for activities that do nothing, or for activities
-    that only call a server action. Activities that do nothing can be used as
-    hubs to gather/dispatch transitions.
-Function
-    The ``function`` kind is for activities that only need to run some Python
-    code, and possibly a server action.
-Stop all
-    The ``stopall`` kind is for activities that will completely stop the
-    workflow instance and mark it as completed. In addition they can also run
-    some Python code.
-Subflow
-    When the kind of the activity is ``subflow``, the activity embeds another
-    workflow instance. When the subflow is completed, the activity is also
-    considered completed.
-
-    By default, the subflow is instanciated for the same record as the parent
-    workflow. It is possible to change that behavior by providing Python code
-    that returns a record ID (of the same data model as the subflow). The
-    embedded subflow instance is then the one of the given record.
diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst
new file mode 100644 (file)
index 0000000..9044c47
--- /dev/null
@@ -0,0 +1,474 @@
+.. highlight:: xml
+
+===============
+Creating themes
+===============
+
+Basic set up
+============
+
+Create a basic theme module with :command:`odoo.py scaffold` and the ``theme``
+template: from the root odoo folder, use
+
+.. code-block:: console
+
+    $ ./odoo.py scaffold -t theme "Dummy Theme" addons
+
+this should create a new folder ``dummy_theme`` in the ``addons`` directory
+with the structure:
+
+.. code-block:: text
+
+    addons/dummy_theme
+    |-- __init__.py
+    |-- __openerp__.py
+    |-- static
+    |   `-- style
+    |       `-- custom.less
+    `-- views
+        |-- options.xml
+        |-- pages.xml
+        `-- snippets.xml
+
+``static/styles`` contains your stylesheet(s), ``views`` contains the various
+XML files describing the theme and theme features to Odoo.
+
+Static Page
+-----------
+
+Creating a new template
+'''''''''''''''''''''''
+
+Create a new file :file:`odoo/addons/theme_dummy/views/pages.xml` and open it.
+
+In odoo, a page means a new template. You don't need special skills, simply
+copy paste the lines::
+
+  <template id="website.hello" name="Homepage" page="True">
+      <t t-call="website.layout">
+          <div id="wrap" class="oe_structure oe_empty">
+          </div>
+      </t>
+  </template>
+
+Refresh the page and feel the hit.
+
+Editing content on a page
+'''''''''''''''''''''''''
+
+You can now add you content! You should always use the Bootstrap structure as
+below::
+
+    <template id="website.hello" name="Homepage" page="True">
+        <t t-call="website.layout">
+            <div id="wrap" class="oe_structure oe_empty">
+                <section>
+                    <div class="container">
+                        <div class="row">
+                            <h1>This is Your Content</h1>
+                            <p>Isn't amazing to edit everything inline?</p>
+                            <hr/>
+                        </div>
+                    </div>
+                </section>
+            </div>
+        </t>
+    </template>
+
+Adding new item in the menu
+'''''''''''''''''''''''''''
+
+Adding these few more lines will put the new page in your menu::
+
+  <record id="hello_menu" model="website.menu">
+      <field name="name">Hello</field>
+      <field name="url">/page/hello</field>
+      <field name="parent_id" ref="website.main_menu"/>
+      <field name="sequence" type="int">20</field>
+  </record>
+
+Congrats! It's online! Now drag and drop some snippets on the page and let's
+style!
+
+Pimp Your Theme
+---------------
+
+Easy styling with less
+''''''''''''''''''''''
+
+In ``odoo/addons/theme_dummy/static`` create a new folder and name it
+``style``. In the new folder ``odoo/addons/theme_dummy/static/style`` create a
+file and name it ``custom.less``. Open ``custom.less`` in the text editor and
+modify these lines as below:
+
+
+.. code-block:: css
+
+   .h1 {
+       color: #215487;
+   }
+   .span {
+       border: 2px solid black;
+       background-color: #eee;
+   }
+
+Refresh the page and feel the hit.
+
+Get the most of the dom
+'''''''''''''''''''''''
+
+Right-Click, inspect element. You can go deeper by styling the main layout
+container. Here we try with the 'wrapwrap' id.
+
+.. code-block:: css
+
+   #wrapwrap {
+        background-color: #222;
+        width: 80%;
+        margin: 0 auto;
+   }
+
+Easy layout with bootstrap
+''''''''''''''''''''''''''
+
+Open :file:`odoo/addons/theme_dummy/views/pages.xml` and add a new section::
+
+  <section>
+      <div class="container">
+          <div class="row">
+              <div class="alert alert-primary" role="alert">
+                  <a href="#" class="alert-link">...</a>
+              </div>
+              <div class="col-md-6 bg-blue">
+                  <h2>BLUE it!</h2>
+              </div>
+              <div class="col-md-6 bg-green">
+                  <h2>GREEN THAT!</h2>
+              </div>
+          </div>
+      </div>
+  </section>
+
+Refresh the page and check how it looks.
+
+The background of the alert component is the default Bootstrap primary color.
+The two other div your created have no custom styles applied yet.  Open
+:file:`odoo/addons/theme_dummy/static/style/custom.less` and add these lines:
+
+.. code-block:: css
+
+        @brand-primary: #1abc9c;
+        @color-blue: #3498db;
+        @color-green: #2ecc71;
+
+        .bg-blue { background: @color-blue; }
+        .bg-green { background: @color-green; }
+
+        .h2 { color: white; }
+
+As you see, the default primary has changed and your new colors are shining!
+
+Build Your First Snippet
+------------------------
+
+Setting up __openerp__.py
+'''''''''''''''''''''''''
+
+Open ``__openerp__.py`` and add a new line as below:
+
+.. code-block:: python
+
+   {
+       'name': 'Dummy Theme',
+       'description': 'Dummy Theme',
+       'category': 'Website',
+       'version': '1.0',
+       'author': 'OpenERP SA',
+       'depends': ['website'],
+       'data': [
+           'views/snippets.xml',
+       ],
+       'application': True,
+   }
+
+In ``odoo/addons/theme_learn/views`` create a new xml file, name it
+``snippets.xml`` and open it in a text editor
+
+Add your snippet in the menu
+''''''''''''''''''''''''''''
+
+Before typing your html code, you need to locate it in the WEBb. drop-down
+menu.  In this case, we will add it at the end of the Structure section::
+
+  <template id="snippets" inherit_id="website.snippets" name="Clean Theme snippets">
+    <xpath expr="//div[@id='snippet_structure']" position="inside">
+    </xpath>
+  </template>
+
+Now open a new div, do not give it any id or classes. It will contain your
+snippet::
+
+    <xpath expr="//div[@id='snippet_structure']" position="inside">
+        <div>
+        </div>
+    </xpath>
+
+A thumbnail is also needed to create a more attractive link in the menu. You
+can use labels to focus on your themes snippets.  Simply add a new div with
+the class ``oe_snippet_thumbnail`` and add your thumbnail image (100x79px)::
+
+  <xpath expr="//div[@id='snippet_structure']" position="inside">
+      <div>
+          <div class="oe_snippet_thumbnail">
+              <img class="oe_snippet_thumbnail_img" src="/theme_Dummy/static/img/blocks/block_title.png"/>
+              <span class="oe_snippet_thumbnail_title">SNIP IT!</span>
+          </div>
+      </div>
+  </xpath>
+
+And voila! Your new snippet is now ready to use. Just drag and drop it on your
+page to see it in action.
+
+The snippet body
+''''''''''''''''
+
+A snippet has to be in a section with the class ``oe_snippet_body`` to work
+correctly.  As Odoo use the Bootstrap framework, you have use containers and
+rows to hold your content. Please refer the the Bootstrap documentation::
+
+  <xpath expr="//div[@id='snippet_structure']" position="inside">
+      <div>
+          <div class="oe_snippet_thumbnail">
+              <img class="oe_snippet_thumbnail_img" src="/theme_Dummy/static/img/blocks/block_title.png"/>
+              <span class="oe_snippet_thumbnail_title">SNIP IT!</span>
+          </div>
+
+          <section class="oe_snippet_body fw_categories">
+              <div class="container">
+                  <div class="row">
+                  </div>
+              </div>
+          </section>
+      </div>
+  </xpath>
+
+Inside your fresh new row, add some bootstraped contents::
+
+  <div class="col-md-12 text-center mt32 mb32">
+      <h2>A great Title</h2>
+      <h3 class="text-muted ">And a great subtitle too</h3>
+      <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. </p>
+  </div>
+
+
+Adding images to your snippet
+'''''''''''''''''''''''''''''
+
+You can easely add images in your snippets simply by setting up css
+backgrounds images.
+
+In ``odoo/addons/theme_dummy/static/`` create a new folder and name it
+``img``. Put your images there, in sub-folders if needed.  Open
+:file:`odoo/addons/theme_dummy/static/style/custom.less`, add these lines
+
+.. code-block:: css
+
+   @img-01: url("../img/img-boy.png");
+   .dummy-boy { background-image: @img-01; }
+
+   @img-02: url("../img/img-girl.png");
+   .dummy-girl { background-image: @img-02; }
+
+In :file:`odoo/addons/theme_dummy/views/pages.xml` change the correspondant
+lines as below::
+
+    <section>
+        <div class="container">
+            <div class="row dummy-bg">
+                <div class="alert alert-primary" role="alert">
+                <a href="#" class="alert-link">...</a>
+                </div>
+                <div class="col-md-6">
+                <h2>BLUE it!</h2>
+                    <div class="dummy-boy">
+                    </div>
+                </div>
+                <div class="col-md-6">
+                <h2>GREEN THAT!</h2>
+                    <div class="dummy-girl">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+
+Your new snippet is now ready to use. Just drag and drop it on your page to
+see it in action.
+
+Advanced Customization
+======================
+
+Defining Your Theme Options
+---------------------------
+
+Understanding XPath
+'''''''''''''''''''
+
+As your stylesheets are running on the whole website, giving more option to
+your snippets and applying them independently will push your design
+forward. In ``odoo/addons/theme_dummy/views/`` create a new file, name it
+``options.xml`` and add these lines::
+
+    <template id="gourman_website_options_pattern" inherit_id="website.snippet_options">
+        <xpath expr="//div[@data-option='dummy_options']//ul" position="after">
+        </xpath>
+    </template>
+
+Explain xpath
+"""""""""""""
+
+.. TODO:: syntax not correct (see website examples) 
+
+Your option menu is now correctly set in the database, you can create an new dropdown menu.
+
+Let's say yout want three options which will change the text color and the background.
+In option.xml, add these lines inside the xpath::
+
+      <li data-check_class="text-purple"><a>YOUR OPTION 1</a></li>
+      <li class="dropdown-submenu">
+          <a tabindex="-1" href="#">Your sub option</a>
+          <ul class="dropdown-menu">
+            <li data-select_class="bg-yellow"><a>YOUR OPTION 2</a></li>
+            <li data-select_class="text-light-bg-dark"><a>YOUR OPTION 3</a></li>
+            <li data-select_class=""><a>None</a></li>
+          </ul>
+      <li>
+
+Simple less css options
+'''''''''''''''''''''''
+
+In order to see these options in action, you have to write some new css
+classes. Open custom.css and add this new lines
+
+.. code-block:: css
+
+    @color-purple: #2ecc71;
+    @color-yellow: #2ecc71;
+
+    .text-purple { color: @color-purple; }
+    .bg-yellow { background-color: @color-yellow;}
+    .text-light-bg-dark { color: #eee; background-color: #222;}
+
+Refresh the page. Select a snippet and click Customize. Choose one of your new
+options apply it.
+
+XPath & inherits
+''''''''''''''''
+
+You can also add images in your variables and use them on certain part of your
+pages, snippets or any html element.
+
+In :file:`odoo/addons/theme_dummy/static/style/custom.css` add these new lines
+
+.. code:: css
+
+    @bg-01: url("../img/background/bg-blur.jpg");
+
+    .bg-01 {
+        background-image: @bg-01;
+    }
+
+Now that you have set the background image, you can decide how and where the
+user can use it, for example, on a simple div.
+
+Open :file:`odoo/addons/theme_dummy/views/options.xml` and add this new xpath::
+
+  <xpath expr="//div[@data-option='background-dummy']//ul" position="after">
+      <ul class="dropdown-menu">
+          <li data-value="bg-01">
+              <a>Image 1</a>
+          </li>
+      </ul>
+  </xpath>
+
+Your option is ready to be applied but you want it to be shown only a certain
+part of a snippet.
+
+Open :file:`odoo/addons/theme_dummy/views/snippets.xml` and add a new snippet
+with the method we learned previously::
+
+    <xpath expr="//div[@id='snippet_structure']" position="inside">
+        <div>
+        <!-- Add a Thumbnail in the Website Builder drop-down menu -->
+            <div class="oe_snippet_thumbnail">
+                <img class="oe_snippet_thumbnail_img" src="/theme_Dummy/static/img/blocks/block_title.png"/>
+                <span class="oe_snippet_thumbnail_title">Test OPTION</span>
+            </div>
+        <!-- Your Snippet content -->
+            <section class="oe_snippet_body fw_categories">
+                <div class="container">
+                    <div class="row">
+                        <div class="col-md-6 text-center mt32 mb32">
+                            <h2>NO OPTION</h2>
+                            <p>OFF</p>
+                        </div>
+                        <div class="col-md-6 text-center mt32 mb32 test-option">
+                            <h2>OPTION</h2>
+                            <p>This div has the 'test-option' class</p>
+                        </div>
+                    </div>
+                </div>
+            </section>
+        </div>
+    </xpath>
+
+As you see, the second ``col-md`` has a class named ``test-option``.  We are
+going to specify where this option can be turned on by adding the
+``data-selector`` attribute.
+
+Go back to your ``options.xml`` files, add these new lines::
+
+  <xpath expr="//div[@data-option='background-dummy']" position="attributes">
+      <attribute name="data-selector">test-option</attribute>
+  </xpath>
+
+Refresh your browser. You should now be able to add your image background on
+the left div only.  The option is now available on each section but also on
+the left div with the custom class.
+
+The Image Database
+------------------
+
+Modifying the image database
+''''''''''''''''''''''''''''
+
+Odoo provides its own image library but you certainly want to adapt it to your
+design.  Do not use the Media Manager uploading Tool to add image in your
+theme. The images url's will be lost on reload!  Instead of uploading your
+images, you can create your own library and disable the old ones.
+
+In ``odoo/addons/theme_dummy/views/`` create a new file, name it
+``images.xml`` and add these lines::
+
+  <record id="image_bg_blue" model="ir.attachment">
+      <field name="name">bg_blue.jpg</field>
+      <field name="datas_fname">bg_blue.jpg</field>
+      <field name="res_model">ir.ui.view</field>
+      <field name="type">url</field>
+      <field name="url">/theme_clean/static/img/library/bg/bg_blue.jpg</field>
+  </record>
+
+Your images is now available in your Media Manager.  And your Theme has a
+total new look.
+
+Theme Selector
+==============
+
+Set Up
+------
+
+Understanding theme variants
+''''''''''''''''''''''''''''
+
+Combining theme variants
+''''''''''''''''''''''''
index 3d3e06d..b04532f 100644 (file)
@@ -9,8 +9,6 @@ Building a website
     * This guide assumes `basic knowledge of Python
       <http://docs.python.org/2/tutorial/>`_
     * This guide assumes an installed Odoo
-    * For production deployment, see the :ref:`dedicated deployment guides
-      <guides/deployment>`
 
 Creating a basic module
 =======================
index 07cb8d7..85fdcbc 100644 (file)
Binary files a/doc/images/inheritance_methods.png and b/doc/images/inheritance_methods.png differ
index afaabc4..65a8d42 100644 (file)
Binary files a/doc/images/view-on-github.png and b/doc/images/view-on-github.png differ
index 2bb0aaa..3c16960 100644 (file)
@@ -21,8 +21,6 @@ The documentation is currently organized in four sections:
 
 * :doc:`tutorials`, aimed at introducing the primary areas of developing Odoo
   modules
-* :doc:`guides`, didactic documents covering more specific and specialized
-  areas of Odoo, trying to solve more specific problems
 * :doc:`reference`, which ought be the complete and canonical documentation
   for Odoo subsystems
 * :doc:`modules`, documenting useful specialized modules and integration
@@ -36,7 +34,6 @@ The documentation is currently organized in four sections:
     :hidden:
 
     tutorials
-    guides
     reference
     modules
 
index a3dd978..3e1597d 100644 (file)
@@ -19,3 +19,4 @@ Reference
     reference/javascript
 
     reference/reports
+    reference/workflows
diff --git a/doc/reference/forms/header.png b/doc/reference/forms/header.png
new file mode 100644 (file)
index 0000000..bf97b8c
Binary files /dev/null and b/doc/reference/forms/header.png differ
diff --git a/doc/reference/forms/header2.png b/doc/reference/forms/header2.png
new file mode 100644 (file)
index 0000000..0d0dbbf
Binary files /dev/null and b/doc/reference/forms/header2.png differ
diff --git a/doc/reference/forms/header3.png b/doc/reference/forms/header3.png
new file mode 100644 (file)
index 0000000..17e8fb9
Binary files /dev/null and b/doc/reference/forms/header3.png differ
diff --git a/doc/reference/forms/nosheet.png b/doc/reference/forms/nosheet.png
new file mode 100644 (file)
index 0000000..99d915c
Binary files /dev/null and b/doc/reference/forms/nosheet.png differ
diff --git a/doc/reference/forms/oppreadonly.png b/doc/reference/forms/oppreadonly.png
new file mode 100644 (file)
index 0000000..447e7bb
Binary files /dev/null and b/doc/reference/forms/oppreadonly.png differ
diff --git a/doc/reference/forms/placeholder.png b/doc/reference/forms/placeholder.png
new file mode 100644 (file)
index 0000000..ab7d592
Binary files /dev/null and b/doc/reference/forms/placeholder.png differ
diff --git a/doc/reference/forms/screenshot-00.png b/doc/reference/forms/screenshot-00.png
new file mode 100644 (file)
index 0000000..dca05af
Binary files /dev/null and b/doc/reference/forms/screenshot-00.png differ
diff --git a/doc/reference/forms/screenshot-01.png b/doc/reference/forms/screenshot-01.png
new file mode 100644 (file)
index 0000000..464a237
Binary files /dev/null and b/doc/reference/forms/screenshot-01.png differ
diff --git a/doc/reference/forms/screenshot-02.png b/doc/reference/forms/screenshot-02.png
new file mode 100644 (file)
index 0000000..ea6ec77
Binary files /dev/null and b/doc/reference/forms/screenshot-02.png differ
diff --git a/doc/reference/forms/screenshot-03.png b/doc/reference/forms/screenshot-03.png
new file mode 100644 (file)
index 0000000..3a59801
Binary files /dev/null and b/doc/reference/forms/screenshot-03.png differ
diff --git a/doc/reference/forms/screenshot-04.png b/doc/reference/forms/screenshot-04.png
new file mode 100644 (file)
index 0000000..b0de67d
Binary files /dev/null and b/doc/reference/forms/screenshot-04.png differ
diff --git a/doc/reference/forms/sheet.png b/doc/reference/forms/sheet.png
new file mode 100644 (file)
index 0000000..dee87a0
Binary files /dev/null and b/doc/reference/forms/sheet.png differ
diff --git a/doc/reference/forms/status.png b/doc/reference/forms/status.png
new file mode 100644 (file)
index 0000000..4f4a0cb
Binary files /dev/null and b/doc/reference/forms/status.png differ
diff --git a/doc/reference/forms/status1.png b/doc/reference/forms/status1.png
new file mode 100644 (file)
index 0000000..7ccd80b
Binary files /dev/null and b/doc/reference/forms/status1.png differ
diff --git a/doc/reference/forms/status2.png b/doc/reference/forms/status2.png
new file mode 100644 (file)
index 0000000..b0d01bd
Binary files /dev/null and b/doc/reference/forms/status2.png differ
diff --git a/doc/reference/forms/wizard-popup.png b/doc/reference/forms/wizard-popup.png
new file mode 100644 (file)
index 0000000..d36549f
Binary files /dev/null and b/doc/reference/forms/wizard-popup.png differ
index bd48e9d..9991fce 100644 (file)
Binary files a/doc/reference/images/runner.png and b/doc/reference/images/runner.png differ
index 38ea294..ff8f5bf 100644 (file)
Binary files a/doc/reference/images/runner2.png and b/doc/reference/images/runner2.png differ
index 84083d9..bc3ce8b 100644 (file)
Binary files a/doc/reference/images/tests.png and b/doc/reference/images/tests.png differ
index c8a6f8a..361fb7f 100644 (file)
Binary files a/doc/reference/images/tests2.png and b/doc/reference/images/tests2.png differ
index 247f707..d323360 100644 (file)
Binary files a/doc/reference/images/tests3.png and b/doc/reference/images/tests3.png differ
index 78211ff..7dfbfb1 100644 (file)
@@ -65,9 +65,9 @@ render_html method.  Classically, this method returns a call to the original
             report_obj = self.env['report']
             report = report_obj._get_report_from_name('<<module.reportname>>')
             docargs = {
-                'doc_ids': ids,
+                'doc_ids': self._ids,
                 'doc_model': report.model,
-                'docs': self.env[report.model].browse(ids),
+                'docs': self,
             }
             return report_obj.render('<<module.reportname>>', docargs)
 
index cf4e5e8..9eaab61 100644 (file)
@@ -1,3 +1,5 @@
+.. highlight:: xml
+
 .. _reference/views:
 
 =====
@@ -6,8 +8,8 @@ Views
 
 .. _reference/views/structure:
 
-Structure
-=========
+Common Structure
+================
 
 View objects expose a number of fields, they are optional unless specified
 otherwise)
@@ -26,8 +28,7 @@ otherwise)
     ``priority`` also defines the order of application during :ref:`view
     inheritance <reference/views/inheritance>`
 ``arch``
-    the description of the view's layout, see
-    :ref:`reference/views/architecture`
+    the description of the view's layout
 ``groups_id``
     :class:`~openerp.fields.Many2many` field to the groups allowed to view/use
     the current view
@@ -110,25 +111,10 @@ the matched node should be altered:
 
 A view's specs are applied sequentially.
 
-.. _reference/views/architecture:
-
-Architecture structures
-=======================
-
-Although they are all expressed as XML and have common points (most commonly
-the presence of ``<field>`` elements), each view has its own ``arch``
-structure with a specific root elements, semantics and affordances.
-
-Most views accept the ``create``, ``edit`` and ``delete`` attributes on their
-root element, when applicable this is used to disable the corresponding action
-from the view (hide the relevant buttons or avoid displaying an interface to
-perform it). May be set to ``true`` or ``false``. Setting them to ``true``
-will override their auto-generation from access-rights.
-
 .. _reference/views/list:
 
 Lists
------
+=====
 
 The root element of list views is ``<tree>``\ [#treehistory]_. The list view's
 root can have the following attributes:
@@ -295,14 +281,14 @@ Possible children elements of the list view are:
 .. _reference/views/form:
 
 Forms
------
+=====
 
 Form views are used to display the data from a single record. Their root
 element is ``<form>``. They are composed of regular HTML_ with additional
 structural and semantic components.
 
 Structural components
-'''''''''''''''''''''
+---------------------
 
 Structural components provide structure or "visual" features with little
 logic. They are used as elements or sets of elements in form views.
@@ -348,7 +334,7 @@ logic. They are used as elements or sets of elements in form views.
   itself, generally used to display workflow buttons and status widgets
 
 Semantic components
-'''''''''''''''''''
+-------------------
 
 Semantic components tie into and allow interaction with the Odoo
 system. Available semantic components are:
@@ -437,10 +423,319 @@ system. Available semantic components are:
 
 .. todo:: widgets?
 
+Business Views guidelines
+-------------------------
+
+.. sectionauthor:: Aline Preillon, Raphael Collet
+
+Business views are targeted at regular users, not advanced users.  Examples
+are: Opportunities, Products, Partners, Tasks, Projects, etc.
+
+.. image:: forms/oppreadonly.png
+   :class: img-responsive
+
+In general, a business view is composed of
+
+1. a status bar on top (with technical or business flow),
+2. a sheet in the middle (the form itself),
+3. a bottom part with History and Comments.
+
+Technically, the new form views are structured as follows in XML::
+
+    <form>
+        <header> ... content of the status bar  ... </header>
+        <sheet>  ... content of the sheet       ... </sheet>
+        <div class="oe_chatter"> ... content of the bottom part ... </div>
+    </form>
+
+The Status Bar
+''''''''''''''
+
+The purpose of the status bar is to show the status of the current record and
+the action buttons.
+
+.. image:: forms/status.png
+   :class: img-responsive
+
+The Buttons
+...........
+
+The order of buttons follows the business flow. For instance, in a sale order,
+the logical steps are:
+
+1. Send the quotation
+2. Confirm the quotation
+3. Create the final invoice
+4. Send the goods
+
+Highlighted buttons (in red by default) emphasize the logical next step, to
+help the user. It is usually the first active button. On the other hand,
+:guilabel:`cancel` buttons *must* remain grey (normal).  For instance, in
+Invoice the button :guilabel:`Refund` must never be red.
+
+Technically, buttons are highlighted by adding the class "oe_highlight"::
+
+    <button class="oe_highlight" name="..." type="..." states="..."/>
+
+The Status
+..........
+
+Uses the ``statusbar`` widget, and shows the current state in red. States
+common to all flows (for instance, a sale order begins as a quotation, then we
+send it, then it becomes a full sale order, and finally it is done) should be
+visible at all times but exceptions or states depending on particular sub-flow
+should only be visible when current.
+
+.. image:: forms/status1.png
+   :class: img-responsive
+
+.. image:: forms/status2.png
+   :class: img-responsive
+
+The states are shown following the order used in the field (the list in a
+selection field, etc). States that are always visible are specified with the
+attribute ``statusbar_visible``.
+
+``statusbar_colors`` can be used to give a custom color to specific states.
+
+::
+
+    <field name="state" widget="statusbar"
+        statusbar_visible="draft,sent,progress,invoiced,done"
+        statusbar_colors="{'shipping_except':'red','waiting_date':'blue'}"/>
+
+The Sheet
+'''''''''
+
+All business views should look like a printed sheet:
+
+.. image:: forms/sheet.png
+   :class: img-responsive
+
+1. Elements inside a ``<form>`` or ``<page>`` do not define groups, elements
+   inside them are laid out according to normal HTML rules. They content can
+   be explicitly grouped using ``<group>`` or regular ``<div>`` elements.
+2. By default, the element ``<group>`` defines two columns inside, unless an
+   attribute ``col="n"`` is used.  The columns have the same width (1/n th of
+   the group's width). Use a ``<group>`` element to produce a column of fields.
+3. To give a title to a section, add a ``string`` attribute to a ``<group>`` element::
+
+     <group string="Time-sensitive operations">
+
+   this replaces the former use of ``<separator string="XXX"/>``.
+4. The ``<field>`` element does not produce a label, except as direct children
+   of a ``<group>`` element\ [#backwards-compatibility]_.  Use :samp:`<label
+   for="{field_name}>` to produce a label of a field.
+
+Sheet Headers
+.............
+
+Some sheets have headers with one or more fields, and the labels of those
+fields are only shown in edit mode.
+
+.. list-table::
+   :header-rows: 1
+
+   * - View mode
+     - Edit mode
+   * - .. image:: forms/header.png
+          :class: img-responsive
+     - .. image:: forms/header2.png
+          :class: img-responsive
+
+Use HTML text, ``<div>``, ``<h1>``, ``<h2>``… to produce nice headers, and
+``<label>`` with the class ``oe_edit_only`` to only display the field's label
+in edit mode. The class ``oe_inline`` will make fields inline (instead of
+blocks): content following the field will be displayed on the same line rather
+than on the line below it. The form above is produced by the following XML::
+
+    <label for="name" class="oe_edit_only"/>
+    <h1><field name="name"/></h1>
+
+    <label for="planned_revenue" class="oe_edit_only"/>
+    <h2>
+        <field name="planned_revenue" class="oe_inline"/>
+        <field name="company_currency" class="oe_inline oe_edit_only"/> at
+        <field name="probability" class="oe_inline"/> % success rate
+    </h2>
+
+Button Box
+..........
+
+Many relevant actions or links can be displayed in the form. For example, in
+Opportunity form, the actions "Schedule a Call" and "Schedule a Meeting" take
+an important place in the use of the CRM. Instead of placing them in the
+"More" menu, put them directly in the sheet as buttons (on the top right) to
+make them more visible and more easily accessible.
+
+.. image:: forms/header3.png
+   :class: img-responsive
+
+Technically, the buttons are placed inside a <div> to group them as a block on
+the right-hand side of the sheet.
+
+::
+
+    <div class="oe_button_box oe_right">
+        <button string="Schedule/Log Call" name="..." type="action"/>
+        <button string="Schedule Meeting" name="action_makeMeeting" type="object"/>
+    </div>
+
+Groups and Titles
+.................
+
+A column of fields is now produced with a ``<group>`` element, with an
+optional title.
+
+.. image:: forms/screenshot-03.png
+   :class: img-responsive
+
+::
+
+    <group string="Payment Options">
+        <field name="writeoff_amount"/>
+        <field name="payment_option"/>
+    </group>
+
+It is recommended to have two columns of fields on the form. For this, simply
+put the ``<group>`` elements that contain the fields inside a top-level
+``<group>`` element.
+
+To make :ref:`view extension <reference/views/inheritance>` simpler, it is
+recommended to put a ``name`` attribute on ``<group>`` elements, so new fields
+can easily be added at the right place.
+
+Special Case: Subtotals
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Some classes are defined to render subtotals like in invoice forms:
+
+.. image:: forms/screenshot-00.png
+   :class: img-responsive
+
+::
+
+    <group class="oe_subtotal_footer">
+        <field name="amount_untaxed"/>
+        <field name="amount_tax"/>
+        <field name="amount_total" class="oe_subtotal_footer_separator"/>
+        <field name="residual" style="margin-top: 10px"/>
+    </group>
+
+Placeholders and Inline Fields
+..............................
+
+Sometimes field labels make the form too complex. One can omit field labels,
+and instead put a placeholder inside the field. The placeholder text is
+visible only when the field is empty. The placeholder should tell what to
+place inside the field, it *must not* be an example as they are often confused
+with filled data.
+
+One can also group fields together by rendering them "inline" inside an
+explicit block element like `<div>``. This allows grouping semantically
+related fields as if they were a single (composite) fields.
+
+The following example, taken from the *Leads* form, shows both placeholders and
+inline fields (zip and city).
+
+.. list-table::
+   :header-rows: 1
+
+   * - Edit mode
+     - View mode
+   * - .. image:: forms/placeholder.png
+          :class: img-responsive
+     - .. image:: forms/screenshot-01.png
+          :class: img-responsive
+
+::
+
+    <group>
+        <label for="street" string="Address"/>
+        <div>
+            <field name="street" placeholder="Street..."/>
+            <field name="street2"/>
+            <div>
+                <field name="zip" class="oe_inline" placeholder="ZIP"/>
+                <field name="city" class="oe_inline" placeholder="City"/>
+            </div>
+            <field name="state_id" placeholder="State"/>
+            <field name="country_id" placeholder="Country"/>
+        </div>
+    </group>
+
+Images
+......
+
+Images, like avatars, should be displayed on the right of the sheet.  The
+product form looks like:
+
+.. image:: forms/screenshot-02.png
+   :class: img-responsive
+
+The form above contains a <sheet> element that starts with:
+
+::
+
+    <field name="product_image" widget="image" class="oe_avatar oe_right"/>
+
+Tags
+....
+
+Most :class:`~openerp.fields.Many2many` fields, like categories, are better
+rendered as a list of tags. Use the widget ``many2many_tags`` for this:
+
+.. image:: forms/screenshot-04.png
+   :class: img-responsive
+
+::
+
+    <field name="category_id" widget="many2many_tags"/>
+
+Configuration forms guidelines
+------------------------------
+
+Examples of configuration forms: Stages, Leave Type, etc.  This concerns all
+menu items under Configuration of each application (like Sales/Configuration).
+
+.. image:: forms/nosheet.png
+   :class: img-responsive
+
+1. no header (because no state, no workflow, no button)
+2. no sheet
+
+Dialog forms guidelines
+-----------------------
+
+Example: "Schedule a Call" from an opportunity.
+
+.. image:: forms/wizard-popup.png
+   :class: img-responsive
+
+1. avoid separators (the title is already in the popup title bar, so another
+   separator is not relevant)
+2. avoid cancel buttons (user generally close the popup window to get the same
+   effect)
+3. action buttons must be highlighted (red)
+4. when there is a text area, use a placeholder instead of a label or a
+   separator
+5. like in regular form views, put buttons in the <header> element
+
+Configuration Wizards guidelines
+--------------------------------
+
+Example: Settings / Configuration / Sales.
+
+1. always in line (no popup)
+2. no sheet
+3. keep the cancel button (users cannot close the window)
+4. the button "Apply" must be red
+
+
 .. _reference/views/graph:
 
 Graphs
-------
+======
 
 The graph view is used to visualize aggregations over a number of records or
 record groups. Its root element is ``<graph>`` which can take the following
@@ -459,8 +754,7 @@ following attributes:
 
 ``name`` (required)
   the name of a field to use in a graph view. If used for grouping (rather
-  than aggregating), can be augmented with a
-  :ref:`reference/views/graph/functions`
+  than aggregating)
 
 ``type``
   indicates whether the field should be used as a grouping criteria or as an
@@ -475,26 +769,20 @@ following attributes:
   ``measure``
     field to aggregate within a group
 
+``interval``
+  on date and datetime fields, groups by the specified interval (``day``,
+  ``week``, ``month``, ``quarter`` or ``year``) instead of grouping on the
+  specific datetime (fixed second resolution) or date (fixed day resolution).
+
 .. warning::
 
    graph view aggregations are performed on database content, non-stored
    function fields can not be used in graph views
 
-.. _reference/views/graph/functions:
-
-Grouping function
-'''''''''''''''''
-
-Field names in graph views can be postfixed with a grouping function using the
-form :samp:`{field_name}:{function}`. As of 8.0, only date and datetime fields
-support grouping functions. The available grouping functions are ``day``,
-``week``, ``month``, ``quarter`` and ``year``. By default, date and datetime
-fields are grouped month-wise.
-
 .. _reference/views/kanban:
 
 Kanban
-------
+======
 
 The kanban view is a `kanban board`_ visualisation: it displays records as
 "cards", halfway between a :ref:`list view <reference/views/list>` and a
@@ -590,7 +878,7 @@ Possible children of the view element are:
        * kanban structures/widgets (vignette, details, ...)
 
 Javascript API
-''''''''''''''
+--------------
 
 .. js:class:: KanbanRecord
 
@@ -630,7 +918,7 @@ Javascript API
 .. _reference/views/calendar:
 
 Calendar
---------
+========
 
 Calendar views display records as events in a daily, weekly or monthly
 calendar. Their root element is ``<calendar>``. Available attributes on the
@@ -685,7 +973,7 @@ calendar view are:
 .. _reference/views/gantt:
 
 Gantt
------
+=====
 
 Gantt views appropriately display Gantt charts (for scheduling).
 
@@ -724,7 +1012,7 @@ take the following attributes:
 .. _reference/views/diagram:
 
 Diagram
--------
+=======
 
 The diagram view can be used to display directed graphs of records. The root
 element is ``<diagram>`` and takes no attributes.
@@ -768,7 +1056,7 @@ Possible children of the diagram view are:
 .. _reference/views/search:
 
 Search
-------
+======
 
 Search views are a break from previous view types in that they don't display
 *content*: although they apply to a specific model, they are used to filter
@@ -869,7 +1157,7 @@ Possible children elements of the search view are:
        as inclusively composited: they will be composed with ``OR`` rather
        than the usual ``AND``, e.g.
 
-       .. code-block:: xml
+       ::
 
           <filter domain="[('state', '=', 'draft')]"/>
           <filter domain="[('state', '=', 'done')]"/>
@@ -877,7 +1165,7 @@ Possible children elements of the search view are:
        if both filters are selected, will select the records whose ``state``
        is ``draft`` or ``done``, but
 
-       .. code-block:: xml
+       ::
 
           <filter domain="[('state', '=', 'draft')]"/>
           <separator/>
@@ -895,12 +1183,14 @@ Possible children elements of the search view are:
 .. _reference/views/search/defaults:
 
 Search defaults
-'''''''''''''''
+---------------
 
 Search fields and filters can be configured through the action's ``context``
 using :samp:`search_default_{name}` keys. For fields, the value should be the
 value to set in the field, for filters it's a boolean value. For instance,
-assuming ``foo`` is a field and ``bar`` is a filter an action context of::
+assuming ``foo`` is a field and ``bar`` is a filter an action context of:
+
+.. code-block:: python
 
   {
     'search_default_foo': 'acro',
@@ -913,7 +1203,7 @@ will automatically enable the ``bar`` filter and search the ``foo`` field for
 .. _reference/views/qweb:
 
 QWeb
-----
+====
 
 QWeb views are standard :ref:`reference/qweb` templates inside a view's
 ``arch``. They don't have a specific root element.
@@ -925,6 +1215,7 @@ template's name *must* match the view's complete (including module name)
 :ref:`reference/data/template` should be used as a shortcut to define QWeb
 views.
 
+.. [#backwards-compatibility] for backwards compatibility reasons
 .. [#hasclass] an extension function is added for simpler matching in QWeb
                views: ``hasclass(*classes)`` matches if the context node has
                all the specified classes
diff --git a/doc/reference/workflow/Makefile b/doc/reference/workflow/Makefile
new file mode 100644 (file)
index 0000000..07bbb67
--- /dev/null
@@ -0,0 +1,20 @@
+DOTFILES:=$(wildcard *.dot)
+SVGFILES:=$(patsubst %.dot,%.svg,$(DOTFILES))
+PNGFILES:=$(patsubst %.dot,%.png,$(DOTFILES))
+
+# try to disable implicit rules
+.SUFFIXES:
+
+.PHONY: all clean
+
+all: $(SVGFILES) $(PNGFILES)
+
+# must -f to ignore errors when running clean multiple times in a row
+clean:
+       rm -f *.png *.svg
+
+%.svg: %.dot
+       dot -Tsvg $< > $@
+
+%.png: %.dot
+       dot -Tpng $< > $@
diff --git a/doc/reference/workflow/join.dot b/doc/reference/workflow/join.dot
new file mode 100644 (file)
index 0000000..e413a8a
--- /dev/null
@@ -0,0 +1,10 @@
+digraph join {
+    // dummy sources as support for edges, make invisible and height 0
+    a [style=invis height=0 fontsize=0]
+    b [style=invis height=0 fontsize=0]
+    c [style=invis height=0 fontsize=0]
+
+    a -> Activity
+    b -> Activity
+    c -> Activity
+}
diff --git a/doc/reference/workflow/join.png b/doc/reference/workflow/join.png
new file mode 100644 (file)
index 0000000..cdc0baf
Binary files /dev/null and b/doc/reference/workflow/join.png differ
diff --git a/doc/reference/workflow/join.svg b/doc/reference/workflow/join.svg
new file mode 100644 (file)
index 0000000..1462281
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.38.0 (20140413.2041)
+ -->
+<!-- Title: join Pages: 1 -->
+<svg width="206pt" height="93pt"
+ viewBox="0.00 0.00 206.00 92.73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 88.7279)">
+<title>join</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-88.7279 202,-88.7279 202,4 -4,4"/>
+<!-- a -->
+<!-- Activity -->
+<g id="node4" class="node"><title>Activity</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-18" rx="40.0939" ry="18"/>
+<text text-anchor="middle" x="99" y="-14.3" font-family="Times,serif" font-size="14.00">Activity</text>
+</g>
+<!-- a&#45;&gt;Activity -->
+<g id="edge1" class="edge"><title>a&#45;&gt;Activity</title>
+<path fill="none" stroke="black" d="M33.6445,-71.9778C42.4332,-64.8537 58.4442,-51.875 72.4186,-40.5472"/>
+<polygon fill="black" stroke="black" points="74.7768,-43.1411 80.3411,-34.1251 70.3688,-37.7033 74.7768,-43.1411"/>
+</g>
+<!-- b -->
+<!-- b&#45;&gt;Activity -->
+<g id="edge2" class="edge"><title>b&#45;&gt;Activity</title>
+<path fill="none" stroke="black" d="M99,-71.9778C99,-66.0508 99,-56.0715 99,-46.3619"/>
+<polygon fill="black" stroke="black" points="102.5,-46.1364 99,-36.1364 95.5001,-46.1365 102.5,-46.1364"/>
+</g>
+<!-- c -->
+<!-- c&#45;&gt;Activity -->
+<g id="edge3" class="edge"><title>c&#45;&gt;Activity</title>
+<path fill="none" stroke="black" d="M164.355,-71.9778C155.567,-64.8537 139.556,-51.875 125.581,-40.5472"/>
+<polygon fill="black" stroke="black" points="127.631,-37.7033 117.659,-34.1251 123.223,-43.1411 127.631,-37.7033"/>
+</g>
+</g>
+</svg>
diff --git a/doc/reference/workflow/order_0.dot b/doc/reference/workflow/order_0.dot
new file mode 100644 (file)
index 0000000..3932040
--- /dev/null
@@ -0,0 +1,9 @@
+digraph order {
+    Draft [style=filled fillcolor="#73fa79"]
+    Closed [style=filled fillcolor="#98c7df"]
+    Canceled [style=filled fillcolor="#98c7df"]
+
+    Draft -> Confirmed
+    Confirmed -> Closed
+    Confirmed -> Canceled
+}
diff --git a/doc/reference/workflow/order_0.png b/doc/reference/workflow/order_0.png
new file mode 100644 (file)
index 0000000..7d16f96
Binary files /dev/null and b/doc/reference/workflow/order_0.png differ
diff --git a/doc/reference/workflow/order_0.svg b/doc/reference/workflow/order_0.svg
new file mode 100644 (file)
index 0000000..54a5c05
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.38.0 (20140413.2041)
+ -->
+<!-- Title: order Pages: 1 -->
+<svg width="188pt" height="188pt"
+ viewBox="0.00 0.00 188.24 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
+<title>order</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-184 184.243,-184 184.243,4 -4,4"/>
+<!-- Draft -->
+<g id="node1" class="node"><title>Draft</title>
+<ellipse fill="#73fa79" stroke="black" cx="85.3968" cy="-162" rx="30.5947" ry="18"/>
+<text text-anchor="middle" x="85.3968" y="-158.3" font-family="Times,serif" font-size="14.00">Draft</text>
+</g>
+<!-- Confirmed -->
+<g id="node4" class="node"><title>Confirmed</title>
+<ellipse fill="none" stroke="black" cx="85.3968" cy="-90" rx="51.9908" ry="18"/>
+<text text-anchor="middle" x="85.3968" y="-86.3" font-family="Times,serif" font-size="14.00">Confirmed</text>
+</g>
+<!-- Draft&#45;&gt;Confirmed -->
+<g id="edge1" class="edge"><title>Draft&#45;&gt;Confirmed</title>
+<path fill="none" stroke="black" d="M85.3968,-143.697C85.3968,-135.983 85.3968,-126.712 85.3968,-118.112"/>
+<polygon fill="black" stroke="black" points="88.8969,-118.104 85.3968,-108.104 81.8969,-118.104 88.8969,-118.104"/>
+</g>
+<!-- Closed -->
+<g id="node2" class="node"><title>Closed</title>
+<ellipse fill="#98c7df" stroke="black" cx="36.3968" cy="-18" rx="36.2938" ry="18"/>
+<text text-anchor="middle" x="36.3968" y="-14.3" font-family="Times,serif" font-size="14.00">Closed</text>
+</g>
+<!-- Canceled -->
+<g id="node3" class="node"><title>Canceled</title>
+<ellipse fill="#98c7df" stroke="black" cx="135.397" cy="-18" rx="44.6926" ry="18"/>
+<text text-anchor="middle" x="135.397" y="-14.3" font-family="Times,serif" font-size="14.00">Canceled</text>
+</g>
+<!-- Confirmed&#45;&gt;Closed -->
+<g id="edge2" class="edge"><title>Confirmed&#45;&gt;Closed</title>
+<path fill="none" stroke="black" d="M73.7845,-72.411C67.8042,-63.8677 60.3917,-53.2785 53.7479,-43.7874"/>
+<polygon fill="black" stroke="black" points="56.5277,-41.6552 47.9257,-35.4699 50.7931,-45.6694 56.5277,-41.6552"/>
+</g>
+<!-- Confirmed&#45;&gt;Canceled -->
+<g id="edge3" class="edge"><title>Confirmed&#45;&gt;Canceled</title>
+<path fill="none" stroke="black" d="M97.2461,-72.411C103.348,-63.8677 110.912,-53.2785 117.692,-43.7874"/>
+<polygon fill="black" stroke="black" points="120.668,-45.6416 123.633,-35.4699 114.972,-41.573 120.668,-45.6416"/>
+</g>
+</g>
+</svg>
diff --git a/doc/reference/workflow/order_1.dot b/doc/reference/workflow/order_1.dot
new file mode 100644 (file)
index 0000000..cc20434
--- /dev/null
@@ -0,0 +1,11 @@
+digraph order {
+    Draft [style=filled fillcolor="#73fa79"]
+    Closed [style=filled fillcolor="#98c7df"]
+    Canceled [style=filled fillcolor="#98c7df"]
+
+    Draft -> Confirmed [label="discount <= 15%"]
+    Draft -> Validation [label="discount > 15%"]
+    Validation -> Confirmed [label="Accept"]
+    Confirmed -> Closed
+    Confirmed -> Canceled
+}
diff --git a/doc/reference/workflow/order_1.png b/doc/reference/workflow/order_1.png
new file mode 100644 (file)
index 0000000..0c38acd
Binary files /dev/null and b/doc/reference/workflow/order_1.png differ
diff --git a/doc/reference/workflow/order_1.svg b/doc/reference/workflow/order_1.svg
new file mode 100644 (file)
index 0000000..08f099e
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.38.0 (20140413.2041)
+ -->
+<!-- Title: order Pages: 1 -->
+<svg width="260pt" height="291pt"
+ viewBox="0.00 0.00 259.79 291.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 287)">
+<title>order</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-287 255.792,-287 255.792,4 -4,4"/>
+<!-- Draft -->
+<g id="node1" class="node"><title>Draft</title>
+<ellipse fill="#73fa79" stroke="black" cx="85.3968" cy="-265" rx="30.5947" ry="18"/>
+<text text-anchor="middle" x="85.3968" y="-261.3" font-family="Times,serif" font-size="14.00">Draft</text>
+</g>
+<!-- Confirmed -->
+<g id="node4" class="node"><title>Confirmed</title>
+<ellipse fill="none" stroke="black" cx="85.3968" cy="-91" rx="51.9908" ry="18"/>
+<text text-anchor="middle" x="85.3968" y="-87.3" font-family="Times,serif" font-size="14.00">Confirmed</text>
+</g>
+<!-- Draft&#45;&gt;Confirmed -->
+<g id="edge1" class="edge"><title>Draft&#45;&gt;Confirmed</title>
+<path fill="none" stroke="black" d="M72.5816,-248.399C62.7132,-235.312 49.8641,-215.656 44.3968,-196 40.1092,-180.585 40.1092,-175.415 44.3968,-160 48.7071,-144.503 57.6054,-129.007 66.0142,-116.73"/>
+<polygon fill="black" stroke="black" points="68.9995,-118.572 71.9779,-108.405 63.3089,-114.496 68.9995,-118.572"/>
+<text text-anchor="middle" x="94.3968" y="-174.3" font-family="Times,serif" font-size="14.00">discount &lt;= 15%</text>
+</g>
+<!-- Validation -->
+<g id="node5" class="node"><title>Validation</title>
+<ellipse fill="none" stroke="black" cx="202.397" cy="-178" rx="49.2915" ry="18"/>
+<text text-anchor="middle" x="202.397" y="-174.3" font-family="Times,serif" font-size="14.00">Validation</text>
+</g>
+<!-- Draft&#45;&gt;Validation -->
+<g id="edge2" class="edge"><title>Draft&#45;&gt;Validation</title>
+<path fill="none" stroke="black" d="M103.936,-250.531C122.48,-237.059 151.286,-216.131 172.959,-200.386"/>
+<polygon fill="black" stroke="black" points="175.139,-203.129 181.172,-194.42 171.024,-197.466 175.139,-203.129"/>
+<text text-anchor="middle" x="195.897" y="-217.8" font-family="Times,serif" font-size="14.00">discount &gt; 15%</text>
+</g>
+<!-- Closed -->
+<g id="node2" class="node"><title>Closed</title>
+<ellipse fill="#98c7df" stroke="black" cx="36.3968" cy="-18" rx="36.2938" ry="18"/>
+<text text-anchor="middle" x="36.3968" y="-14.3" font-family="Times,serif" font-size="14.00">Closed</text>
+</g>
+<!-- Canceled -->
+<g id="node3" class="node"><title>Canceled</title>
+<ellipse fill="#98c7df" stroke="black" cx="135.397" cy="-18" rx="44.6926" ry="18"/>
+<text text-anchor="middle" x="135.397" y="-14.3" font-family="Times,serif" font-size="14.00">Canceled</text>
+</g>
+<!-- Confirmed&#45;&gt;Closed -->
+<g id="edge4" class="edge"><title>Confirmed&#45;&gt;Closed</title>
+<path fill="none" stroke="black" d="M73.7845,-73.174C67.7175,-64.3831 60.1766,-53.4564 53.4596,-43.7236"/>
+<polygon fill="black" stroke="black" points="56.1491,-41.4588 47.5884,-35.2165 50.3879,-45.4348 56.1491,-41.4588"/>
+</g>
+<!-- Confirmed&#45;&gt;Canceled -->
+<g id="edge5" class="edge"><title>Confirmed&#45;&gt;Canceled</title>
+<path fill="none" stroke="black" d="M97.2461,-73.174C103.348,-64.5087 110.912,-53.7682 117.692,-44.1415"/>
+<polygon fill="black" stroke="black" points="120.736,-45.8965 123.633,-35.7052 115.013,-41.8661 120.736,-45.8965"/>
+</g>
+<!-- Validation&#45;&gt;Confirmed -->
+<g id="edge3" class="edge"><title>Validation&#45;&gt;Confirmed</title>
+<path fill="none" stroke="black" d="M181.209,-161.607C162.688,-148.152 135.604,-128.475 114.953,-113.472"/>
+<polygon fill="black" stroke="black" points="116.97,-110.612 106.822,-107.566 112.856,-116.275 116.97,-110.612"/>
+<text text-anchor="middle" x="170.397" y="-130.8" font-family="Times,serif" font-size="14.00">Accept</text>
+</g>
+</g>
+</svg>
diff --git a/doc/reference/workflow/split.dot b/doc/reference/workflow/split.dot
new file mode 100644 (file)
index 0000000..b0f3831
--- /dev/null
@@ -0,0 +1,10 @@
+digraph split {
+    // dummy destinations as support for edges, make invisible and height 0
+    a [style=invis height=0 fontsize=0]
+    b [style=invis height=0 fontsize=0]
+    c [style=invis height=0 fontsize=0]
+
+    Activity -> a
+    Activity -> b
+    Activity -> c
+}
diff --git a/doc/reference/workflow/split.png b/doc/reference/workflow/split.png
new file mode 100644 (file)
index 0000000..d9d512c
Binary files /dev/null and b/doc/reference/workflow/split.png differ
diff --git a/doc/reference/workflow/split.svg b/doc/reference/workflow/split.svg
new file mode 100644 (file)
index 0000000..139874f
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.38.0 (20140413.2041)
+ -->
+<!-- Title: split Pages: 1 -->
+<svg width="206pt" height="93pt"
+ viewBox="0.00 0.00 206.00 92.73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 88.7279)">
+<title>split</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-88.7279 202,-88.7279 202,4 -4,4"/>
+<!-- a -->
+<!-- b -->
+<!-- c -->
+<!-- Activity -->
+<g id="node4" class="node"><title>Activity</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-66.7279" rx="40.0939" ry="18"/>
+<text text-anchor="middle" x="99" y="-63.0279" font-family="Times,serif" font-size="14.00">Activity</text>
+</g>
+<!-- Activity&#45;&gt;a -->
+<g id="edge1" class="edge"><title>Activity&#45;&gt;a</title>
+<path fill="none" stroke="black" d="M80.4582,-50.6978C68.3594,-40.8903 52.7894,-28.2691 41.577,-19.1802"/>
+<polygon fill="black" stroke="black" points="43.641,-16.3478 33.6687,-12.7696 39.233,-21.7857 43.641,-16.3478"/>
+</g>
+<!-- Activity&#45;&gt;b -->
+<g id="edge2" class="edge"><title>Activity&#45;&gt;b</title>
+<path fill="none" stroke="black" d="M99,-48.5325C99,-40.494 99,-30.9869 99,-23.1351"/>
+<polygon fill="black" stroke="black" points="102.5,-22.8941 99,-12.8941 95.5001,-22.8942 102.5,-22.8941"/>
+</g>
+<!-- Activity&#45;&gt;c -->
+<g id="edge3" class="edge"><title>Activity&#45;&gt;c</title>
+<path fill="none" stroke="black" d="M117.542,-50.6978C129.641,-40.8903 145.211,-28.2691 156.423,-19.1802"/>
+<polygon fill="black" stroke="black" points="158.767,-21.7857 164.331,-12.7696 154.359,-16.3478 158.767,-21.7857"/>
+</g>
+</g>
+</svg>
diff --git a/doc/reference/workflows.rst b/doc/reference/workflows.rst
new file mode 100644 (file)
index 0000000..210213e
--- /dev/null
@@ -0,0 +1,324 @@
+.. _reference/workflows:
+
+Workflows
+=========
+
+In Odoo, a workflow is a technical artefact to manage a set of "things to
+do" associated to the records of a model. The workflow provides a higher-level
+way to organize tasks to perform with or on a record.
+
+More specifically, a workflow is a directed graph where the nodes are called
+"activities" and the arcs are called "transitions".
+
+- Activities define work that should be done within the Odoo server, such
+  as changing the state of some records, or sending emails.
+- Transitions control how the workflow progresses from activity to activity.
+
+In the definition of a workflow, one can attach conditions, signals, and
+triggers to transitions, so that the behavior of the workflow depends on user
+actions (such as clicking on a button), changes to records, or arbitrary
+Python code.
+
+All in all, Odoo's workflow system provides:
+
+* a description of the evolution of a record (document) over time
+* automatic actions based on various and flexible conditions
+* management of company roles and validation steps
+* management of interactions between objects
+* a visual representation of document flows through their lifecycle
+
+For instance, a basic order could have the following flow:
+
+.. sphinx.ext.graphviz would be nice, but it requires ``dot`` on any machine
+.. where the doc is compiled... otoh this is a pain in the ass because you
+.. need 2 compilation steps (dot -> image and rst -> html) every time
+
+.. image:: workflow/order_0.*
+    :align: center
+
+Orders start in the *Draft* state, can be *Confirmed* by a user, and then
+either shipped (*Closed*) or *Canceled*.
+
+A company using Odoo may want to add discount support to orders, where sales
+staff has discretionary discounting powers up to 15%, but manager validation
+is required for discounts beyond 15%. The workflow can be altered online to
+add the relevant steps without editing Python or XML files:
+
+.. image:: workflow/order_1.*
+    :align: center
+
+Because Activities can perform arbitrary actions, the *Validation* can
+automatically send a validation request to the relevant employee.
+
+.. note:: the order view needs to be modified to add an *Accept Discount*
+          button for managers
+
+Basics
+------
+
+Defining a workflow with data files is straightforward: a record "workflow" is
+given together with records for the activities and the transitions. For
+instance, here is a simple sequence of two activities defined in XML
+
+.. code-block:: xml
+
+    <record id="test_workflow" model="workflow">
+        <field name="name">test.workflow</field>
+        <field name="osv">test.workflow.model</field>
+        <field name="on_create">True</field>
+    </record>
+
+    <record id="activity_a" model="workflow.activity">
+        <field name="wkf_id" ref="test_workflow"/>
+        <field name="flow_start">True</field>
+        <field name="name">a</field>
+        <field name="kind">function</field>
+        <field name="action">print_a()</field>
+    </record>
+    <record id="activity_b" model="workflow.activity">
+        <field name="wkf_id" ref="test_workflow"/>
+        <field name="flow_stop">True</field>
+        <field name="name">b</field>
+        <field name="kind">function</field>
+        <field name="action">print_b()</field>
+    </record>
+
+    <record id="trans_a_b" model="workflow.transition">
+        <field name="act_from" ref="activity_a"/>
+        <field name="act_to" ref="activity_b"/>
+    </record>
+
+A worfklow is always defined with respect to a particular model (the model is
+given by the attribute ``osv`` on the model ``workflow``). Methods specified
+in the activities or transitions will be called on that model.
+
+In the example code above, a workflow called "test_workflow" is created. It is
+made up of two activies, named "a" and "b", and one transition, going from "a"
+to "b".
+
+The first activity has its attribute ``flow_start`` set to ``True`` so that
+Odoo knows where to start the workflow traversal after it is instanciated.
+Because ``on_create`` is set to True on the workflow record, the workflow is
+instanciated for each newly created record. (Otherwise, the workflow should be
+instanciated by other means, such as from some module Python code.)
+
+When the workflow is instanciated, it begins with activity "a". That activity
+is of kind ``function``, which means that the action ``print_a()`` is a method
+call on the model ``test.workflow`` (the usual ``cr, uid, ids, context``
+arguments are passed for you).
+
+The transition between "a" and "b" does not specify any condition. This means
+that the workflow instance immediately goes from "a" to "b" after "a" has been
+processed, and thus also processes activity "b".
+
+Activities
+----------
+
+While the transitions can be seen as the control structures of the workflows,
+activities are the places where everything happens, from changing record
+states to sending email.
+
+Different kinds of activities exist: ``Dummy``, ``Function``, ``Subflow``, and
+``Stop all``, each doing different things when the activity is processed. In
+addition to their kind, activies have other properties, detailed in the next
+sections.
+
+Flow start and flow stop
+''''''''''''''''''''''''
+
+The attribute ``flow_start`` is a boolean value specifying whether the activity
+is processed when the workflow is instanciated. Multiple activities can have
+their attribute ``flow_start`` set to ``True``. When instanciating a workflow
+for a record, Odoo simply processes all of them, and evaluate all their
+outgoing transitions afterwards.
+
+The attribute ``flow_stop`` is a boolean value specifying whether the activity
+stops the workflow instance. A workflow instance is considered completed when
+all its activities with the attribute ``flow_stop`` set to ``True`` are
+completed.
+
+It is important for Odoo to know when a workflow instance is completed. A
+workflow can have an activity that is actually another workflow (called a
+subflow); that activity is completed when the subflow is completed.
+
+Subflow
+'''''''
+
+An activity can embed a complete workflow, called a subflow (the embedding
+workflow is called the parent workflow). The workflow to instanciate is
+specified by attribute ``subflow_id``.
+
+.. note:: In the GUI, that attribute can not be set unless the kind of the
+          activity is ``Subflow``.
+
+The activity is considered completed (and its outgoing transitions ready to be
+evaluated) when the subflow is completed (see attribute ``flow_stop`` above).
+
+Sending a signal from a subflow
+'''''''''''''''''''''''''''''''
+
+When a workflow is embedded in an activity (as a subflow) of a workflow, the
+sublow can send a signal from its own activities to the parent workflow by
+giving a signal name in the attribute ``signal_send``. Odoo processes those
+activities by sending the value of ``signal_send`` prefixed by "subflow."  to
+the parent workflow instance.
+
+In other words, it is possible to react and get transitions in the parent
+workflow as activities are executed in the sublow.
+
+Server actions
+''''''''''''''
+
+An activity can run a "Server Action" by specifying its ID in the attribute
+``action_id``.
+
+Python action
+'''''''''''''
+
+An activity can execute some Python code, given by the attribute ``action``.
+The evaluation environment is the same as the one explained in the section
+`Conditions`_.
+
+Split mode
+''''''''''
+
+After an activity has been processed, Odoo evaluates its transition to reach
+the next activity in the flow.
+
+However if an activity has more than one transition, Odoo must decide which
+activity or activities to follow.
+
+.. image:: workflow/split.*
+    :align: center
+
+This choice is controlled by the ``split_mode`` attribute:
+
+``XOR`` (default)
+    By default, Odoo will use the first transition (in ``sequence`` order)
+    whose condition is satisfied. All other transitions are ignored.
+``OR``
+    In ``OR`` mode, all transitions with a satisfied condition are traversed
+    simultanously. Transitions not yet valid will be ignored, even if they
+    become valid later.
+``AND``
+    In ``AND`` mode, Odoo will wait until *all* transitions are satisfied, and
+    will traverse all of them (much like the ``OR`` mode).
+
+Both ``OR`` and ``AND`` mode will lead to activities being active in the same
+workflow.
+
+Join mode
+'''''''''
+
+Just like outgoing transition conditions can be combined together to decide
+whether they can be traversed or not, incoming transitions can be combined
+together to decide if and when an activity may be processed.
+
+.. image:: workflow/join.*
+    :align: center
+
+The ``join_mode`` attribute controls that behavior:
+
+``XOR`` (default)
+    Any incoming transition enables the activity and starts its processing.
+``AND``
+    The activity is enabled and processed only once *all* incoming transitions
+    have been traversed.
+
+Kinds
+'''''
+
+An activity's kind defines the type of work an activity can perform.
+
+Dummy (``dummy``, default)
+    Do nothing at all, or call a server action. Often used as dispatch or
+    gather "hubs" for transitions.
+Function (``function``)
+    Run some python code, execute a server action.
+Stop all (``stopall``)
+    Completely stops the workflow instance and marks it as completed.
+Subflow (``subflow``)
+    Starts executing an other workflow, once that workflow is completed the
+    activity is done processing.
+
+    By default, the subflow is instanciated for the same record as the parent
+    workflow. It is possible to change that behavior by providing Python code
+    that returns a record ID (of the same data model as the subflow). The
+    embedded subflow instance is then the one of the given record.
+
+
+Transitions
+-----------
+
+Transitions provide the control structures to orchestrate a workflow. When an
+activity is completed, the workflow engine tries to get across transitions
+departing from the completed activity, towards the next activities. In their
+simplest form (as in the example above), they link activities sequentially:
+activities are processed as soon as the activities preceding them are
+completed.
+
+Instead of running all activities in one fell swoop, it is also possible to
+wait on transitions, going through them only when some criteria are met. The
+criteria are the conditions, the signals, and the triggers. They are detailed
+in the following sections.
+
+Conditions
+''''''''''
+
+When an activity has been completed, its outgoing transitions are inspected to
+determine whether it is possible for the workflow instance to proceed through
+them and reach the next activities. When only a condition is defined (i.e., no
+signal or trigger is defined), the condition is evaluated by Odoo, and if
+it evaluates to ``True``, the worklfow instance progresses through the
+transition.  If the condition is not met, it will be reevaluated every time
+the associated record is modified, or by an explicit method call to do it.
+
+By default, the attribute ``condition`` (i.e., the expression to be evaluated)
+is just "True", which trivially evaluates to ``True``. Note that the condition
+may be several lines long; in that case, the value of the last one determines
+whether the transition can be taken.
+
+In the condition evaluation environment, several symbols are conveniently
+defined (in addition to the Odoo ``safe_eval`` environment):
+
+- all the model column names, and
+- all the browse record's attributes.
+
+Signals
+'''''''
+
+In addition to a condition, a transition can specify a signal name. When such
+a signal name is present, the transition is not taken directly, even if the
+condition evaluates to ``True``. Instead the transition blocks, waiting to be
+woken up.
+
+In order to wake up a transition with a defined signal name, the signal must
+be sent to the workflow instance. A common way to send a signal is to use a
+button in the user interface, using the element ``<button/>`` with the signal
+name as the attribute ``name`` of the button. Once the button is clicked, the
+signal is sent to the workflow instance of the current record.
+
+.. note:: The condition is still evaluated when the signal is sent to the
+          workflow instance.
+
+Triggers
+''''''''
+
+With conditions that evaluate to ``False``, transitions are not taken (and
+thus the activity it leads to is not processed immediately). Still, the
+workflow instance can get new chances to progress across that transition by
+providing so-called triggers. The idea is that when the condition is not
+satisfied, triggers are recorded in database. Later, it is possible to wake up
+specifically the workflow instances that installed those triggers, offering
+them to reevaluate their transition conditions. This mechanism makes it
+cheaper to wake up workflow instances by targetting just a few of them (those
+that have installed the triggers) instead of all of them.
+
+Triggers are recorded in database as record IDs (together with the model name)
+and refer to the workflow instance waiting for those records. The transition
+definition provides a model name (attribute ``trigger_model``) and a Python
+expression (attribute ``trigger_expression``) that evaluates to a list of
+record IDs in the given model. Any of those records can wake up the workflow
+instance they are associated with.
+
+.. note:: triggers are not re-installed whenever the transition is re-tried.
index 5cbfec9..a9df051 100644 (file)
@@ -8,3 +8,4 @@ Tutorials
     howtos/website
     howtos/backend
     howtos/web
+    howtos/themes
index ae8f437..7c46361 100644 (file)
@@ -125,6 +125,9 @@ class Field(object):
             ``one2many`` and computed fields, including property fields and
             related fields)
 
+        :param string oldname: the previous name of this field, so that ORM can rename
+            it automatically at migration
+
         .. _field-computed:
 
         .. rubric:: Computed fields
@@ -248,7 +251,7 @@ class Field(object):
 
     automatic = False           # whether the field is automatically created ("magic" field)
     inherited = False           # whether the field is inherited (_inherits)
-    column = None               # the column interfaced by the field
+    column = None               # the column corresponding to the field
     setup_done = False          # whether the field has been set up
 
     name = None                 # name of the field
@@ -314,12 +317,17 @@ class Field(object):
             # by default, related fields are not stored
             attrs['store'] = attrs.get('store', False)
 
+        # fix for function fields overridden by regular columns
+        if not isinstance(attrs.get('column'), (NoneType, fields.function)):
+            attrs.pop('store', None)
+
         for attr, value in attrs.iteritems():
             if not hasattr(self, attr):
                 self._free_attrs.append(attr)
             setattr(self, attr, value)
 
-        if not self.string:
+        if not self.string and not self.related:
+            # related fields get their string from their parent field
             self.string = name.replace('_', ' ').capitalize()
 
         # determine self.default and cls._defaults in a consistent way
@@ -442,7 +450,7 @@ class Field(object):
         self.depends = ('.'.join(self.related),)
         self.compute = self._compute_related
         self.inverse = self._inverse_related
-        if field._description_searchable(env):
+        if field._description_searchable:
             # allow searching on self only if the related field is searchable
             self.search = self._search_related
 
@@ -574,30 +582,7 @@ class Field(object):
         return desc
 
     # properties used by get_description()
-
-    def _description_store(self, env):
-        if self.store:
-            # if the corresponding column is a function field, check the column
-            column = env[self.model_name]._columns.get(self.name)
-            return bool(getattr(column, 'store', True))
-        return False
-
-    def _description_searchable(self, env):
-        if self.store:
-            column = env[self.model_name]._columns.get(self.name)
-            return bool(getattr(column, 'store', True)) or \
-                   bool(getattr(column, '_fnct_search', False))
-        return bool(self.search)
-
-    def _description_sortable(self, env):
-        if self.store:
-            column = env[self.model_name]._columns.get(self.name)
-            return bool(getattr(column, 'store', True))
-        if self.inherited:
-            # self is sortable if the inherited field is itself sortable
-            return self.related_field._description_sortable(env)
-        return False
-
+    _description_store = property(attrgetter('store'))
     _description_manual = property(attrgetter('manual'))
     _description_depends = property(attrgetter('depends'))
     _description_related = property(attrgetter('related'))
@@ -609,6 +594,14 @@ class Field(object):
     _description_change_default = property(attrgetter('change_default'))
     _description_deprecated = property(attrgetter('deprecated'))
 
+    @property
+    def _description_searchable(self):
+        return bool(self.store or self.search or (self.column and self.column._fnct_search))
+
+    @property
+    def _description_sortable(self):
+        return self.store or (self.inherited and self.related_field._description_sortable)
+
     def _description_string(self, env):
         if self.string and env.lang:
             name = "%s,%s" % (self.model_name, self.name)
@@ -630,7 +623,7 @@ class Field(object):
 
     def to_column(self):
         """ return a low-level field object corresponding to `self` """
-        assert self.store
+        assert self.store or self.column
 
         # determine column parameters
         _logger.debug("Create fields._column for Field %s", self)
@@ -644,13 +637,15 @@ class Field(object):
             # company-dependent fields are mapped to former property fields
             args['type'] = self.type
             args['relation'] = self.comodel_name
-            return fields.property(**args)
-
-        if self.column:
+            self.column = fields.property(**args)
+        elif self.column:
             # let the column provide a valid column for the given parameters
-            return self.column.new(**args)
+            self.column = self.column.new(**args)
+        else:
+            # create a fresh new column of the right type
+            self.column = getattr(fields, self.type)(**args)
 
-        return getattr(fields, self.type)(**args)
+        return self.column
 
     # properties used by to_column() to create a column instance
     _column_copy = property(attrgetter('copy'))
@@ -820,8 +815,8 @@ class Field(object):
         """ Determine the value of `self` for `record`. """
         env = record.env
 
-        if self.store and not (self.depends and env.in_draft):
-            # this is a stored field
+        if self.column and not (self.depends and env.in_draft):
+            # this is a stored field or an old-style function field
             if self.depends:
                 # this is a stored computed field, check for recomputation
                 recs = record._recompute_check(self)
@@ -978,9 +973,8 @@ class Float(Field):
         if self.digits:
             assert isinstance(self.digits, (tuple, list)) and len(self.digits) >= 2, \
                 "Float field %s with digits %r, expecting (total, decimal)" % (self, self.digits)
-        if self.store:
-            column = env[self.model_name]._columns[self.name]
-            column.digits_change(env.cr)
+        if self.column:
+            self.column.digits_change(env.cr)
 
     def _setup_regular(self, env):
         super(Float, self)._setup_regular(env)
@@ -1691,11 +1685,10 @@ class Many2many(_RelationalMulti):
     def _setup_regular(self, env):
         super(Many2many, self)._setup_regular(env)
 
-        if self.store and not self.relation:
-            model = env[self.model_name]
-            column = model._columns[self.name]
-            if not isinstance(column, fields.function):
-                self.relation, self.column1, self.column2 = column._sql_names(model)
+        if not self.relation:
+            if isinstance(self.column, fields.many2many):
+                self.relation, self.column1, self.column2 = \
+                    self.column._sql_names(env[self.model_name])
 
         if self.relation:
             m2m = env.registry._m2m
@@ -1724,7 +1717,8 @@ class Id(Field):
         super(Id, self).__init__(type='integer', string=string, **kwargs)
 
     def to_column(self):
-        return fields.integer('ID')
+        self.column = fields.integer('ID')
+        return self.column
 
     def __get__(self, record, owner):
         if record is None:
index 5b1a62f..b301823 100644 (file)
@@ -494,7 +494,7 @@ class JsonRequest(WebRequest):
 
     def _handle_exception(self, exception):
         """Called within an except block to allow converting exceptions
-           to abitrary responses. Anything returned (except None) will
+           to arbitrary responses. Anything returned (except None) will
            be used as response."""
         try:
             return super(JsonRequest, self)._handle_exception(exception)
index 143ac53..48274ea 100644 (file)
@@ -475,7 +475,7 @@ class BaseModel(object):
         # basic setup of field
         field.set_class_name(cls, name)
 
-        if field.store:
+        if field.store or field.column:
             cls._columns[name] = field.to_column()
         else:
             # remove potential column that may be overridden by field
@@ -2980,6 +2980,12 @@ class BaseModel(object):
                 if not partial:
                     raise
 
+        # update columns (fields may have changed), and column_infos
+        for name, field in self._fields.iteritems():
+            if field.column:
+                self._columns[name] = field.to_column()
+        self._inherits_reload()
+
         # group fields by compute to determine field.computed_fields
         fields_by_compute = defaultdict(list)
         for field in self._fields.itervalues():
@@ -3647,7 +3653,7 @@ class BaseModel(object):
         for key, val in vals.iteritems():
             field = self._fields.get(key)
             if field:
-                if field.store or field.inherited:
+                if field.column or field.inherited:
                     old_vals[key] = val
                 if field.inverse and not field.inherited:
                     new_vals[key] = val
@@ -3948,7 +3954,7 @@ class BaseModel(object):
         for key, val in vals.iteritems():
             field = self._fields.get(key)
             if field:
-                if field.store or field.inherited:
+                if field.column or field.inherited:
                     old_vals[key] = val
                 if field.inverse and not field.inherited:
                     new_vals[key] = val
index 679fae0..34672c4 100644 (file)
 
 """
 from collections import Mapping
-from contextlib import contextmanager
 import logging
+import os
 import threading
 
 import openerp
 from .. import SUPERUSER_ID
-from openerp.tools import assertion_report, lazy_property
+from openerp.tools import assertion_report, lazy_property, classproperty, config
+from openerp.tools.lru import LRU
 
 _logger = logging.getLogger(__name__)
 
@@ -259,12 +260,27 @@ class RegistryManager(object):
         registries (essentially database connection/model registry pairs).
 
     """
-    # Mapping between db name and model registry.
-    # Accessed through the methods below.
-    registries = {}
+    _registries = None
     _lock = threading.RLock()
     _saved_lock = None
 
+    @classproperty
+    def registries(cls):
+        if cls._registries is None:
+            size = config.get('registry_lru_size', None)
+            if not size:
+                # Size the LRU depending of the memory limits
+                if os.name != 'posix':
+                    # cannot specify the memory limit soft on windows...
+                    size = 42
+                else:
+                    # On average, a clean registry take 25MB of memory + cache
+                    avgsz = 30 * 1024 * 1024
+                    size = int(config['limit_memory_soft'] / avgsz)
+
+            cls._registries = LRU(size)
+        return cls._registries
+
     @classmethod
     def lock(cls):
         """ Return the current registry lock. """
index 9bf4083..d42544e 100644 (file)
@@ -855,7 +855,7 @@ class expression(object):
                 leaf.leaf = ('id', 'in', table_ids)
                 push(leaf)
 
-            elif not field.store:
+            elif not column:
                 # Non-stored field should provide an implementation of search.
                 if not field.search:
                     # field does not support search!
index 9506a5c..14e1018 100644 (file)
@@ -1292,6 +1292,7 @@ class function(_column):
 
     def to_field_args(self):
         args = super(function, self).to_field_args()
+        args['store'] = bool(self.store)
         if self._type in ('float',):
             args['digits'] = self.digits_compute or self.digits
         elif self._type in ('selection', 'reference'):
index e89284a..197c913 100644 (file)
@@ -249,8 +249,8 @@ class ThreadedServer(CommonServer):
             time.sleep(SLEEP_INTERVAL + number)     # Steve Reich timing style
             registries = openerp.modules.registry.RegistryManager.registries
             _logger.debug('cron%d polling for jobs', number)
-            for db_name, registry in registries.items():
-                while True and registry.ready:
+            for db_name, registry in registries.iteritems():
+                while registry.ready:
                     acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
                     if not acquired:
                         break
index 739c32a..589da7f 100644 (file)
@@ -477,8 +477,6 @@ class ConnectionPool(object):
 
     @locked
     def borrow(self, dsn):
-        self._debug('Borrow connection to %r', dsn)
-
         # free dead and leaked connections
         for i, (cnx, _) in tools.reverse_enumerate(self._connections):
             if cnx.closed:
@@ -503,7 +501,7 @@ class ConnectionPool(object):
                     continue
                 self._connections.pop(i)
                 self._connections.append((cnx, True))
-                self._debug('Existing connection found at index %d', i)
+                self._debug('Borrow existing connection to %r at index %d', cnx.dsn, i)
 
                 return cnx
 
@@ -546,11 +544,15 @@ class ConnectionPool(object):
 
     @locked
     def close_all(self, dsn=None):
-        _logger.info('%r: Close all connections to %r', self, dsn)
+        count = 0
+        last = None
         for i, (cnx, used) in tools.reverse_enumerate(self._connections):
             if dsn is None or cnx._original_dsn == dsn:
                 cnx.close()
-                self._connections.pop(i)
+                last = self._connections.pop(i)[0]
+                count += 1
+        _logger.info('%r: Closed %d connections %s', self, count,
+                    (dsn and last and 'to %r' % last.dsn) or '')
 
 
 class Connection(object):
index baeae0b..0a36166 100644 (file)
@@ -20,7 +20,7 @@
 #
 ##############################################################################
 
-__all__ = ['synchronized', 'lazy_property']
+__all__ = ['synchronized', 'lazy_property', 'classproperty']
 
 from functools import wraps
 from inspect import getsourcefile
@@ -103,4 +103,12 @@ def compose(a, b):
         return a(b(*args, **kwargs))
     return wrapper
 
+
+class _ClassProperty(property):
+    def __get__(self, cls, owner):
+        return self.fget.__get__(None, owner)()
+
+def classproperty(func):
+    return _ClassProperty(classmethod(func))
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: